- ํ ํ๋ก์ ํธ(3์ธ)
- ํ๋ก์ ํธ ๊ธฐ๊ฐ: 2022.02.07 - 2022.02.25
- ํค์๋
- ํ๋ก์ ํธ ์๊ฐ
- ํ๋ก์ ํธ ์ฃผ์๊ธฐ๋ฅ
- ํ๋ก์ ํธ ๊ธฐ์ ์คํ
- Trouble Shooting
- ๊ณ ๋ฏผํ๋ ์
- "์ฌ์ฉ์ ์นํ์ ์ธ UI๋ฅผ ๊ตฌํ"
- "์ฝ์ด๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ๋ ๋งค๋์ ํ์ ๊ตฌํ"
- "์ ๋ชฉ๊ณผ ๋ณธ๋ฌธ์ ํฐํธ๋ฅผ ๋ค๋ฅด๊ฒ ํ์ฌ ๊ตฌ๋ถํ๋ ๊ธฐ๋ฅ ๊ตฌํ"
- "ํด๋ผ์ฐ๋์ ์ ๋ก๋ํ๋ ์์ "
- "ํด๋ผ์ฐ๋์ ๋ค์ด๋ก๋ํ๋ ์์ "
- "๋ค์ด๋ก๋๊ฐ ์งํ์ค์ผ ๋ ๋ทฐ์ ์ํ"
- ์๋กญ๊ฒ ์๊ฒ๋ ๊ฒ
UISplitViewController
DateFormatter
Locale
TimeZone
UITapGestureRecognizer
subscript
Collection
SceneDelegate
NavigationItem
UIBarButtonItem
UITextView
typingAttributes
UITextViewDelegate
Core Data
NSPersistentCloudKitContainer
NSEntityDescription
NSFetchRequest
NSPredicate
NSSortDescriptor
NSManagedObject
NSMutableAttributedString
UIActivityViewController
UIAlertController
popoverPresentationController
UITableView
UISwipeActionsConfiguration
UIContextualAction
insertRows
selectRow
deleteRows
UITableViewCell
setSelected
selectedBackgroundView
viewWillTransition
UIFont
UIFontMetrics
UIFontDescriptor
flatMap
Swift Package Manager
SwifryDropbox
DispatchGroup
FileManager
UIActivityIndicatorView
UISearchController
Localization
NSLocalizedString
Accessibility
Dynamic Type
VoiceOver
Accessibility Inspector
Lite Mode
Dark Mode
ํด๋ผ์ฐ๋ ์๋ฒ์ ๋๊ธฐํ ํ ์ ์๋ ๋ฉ๋ชจ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ฑ์ด์์. โ๏ธ
์์ดํฐ ๋ฐ ์์ดํจ๋์ ๊ธฐ๋ณธ์ฑ์ธ ๋ฉ๋ชจ
์ฑ๊ณผ ํก์ฌํ ๊ธฐ๋ฅ์ ๊ฐ์ง๊ณ ์์ด์. ๐
CoreData๋ฅผ ํตํด ๊ฐ ๋๋ฐ์ด์ค ๋ณ๋ก ๋ฉ๋ชจ ๋ฐ์ดํฐ๋ฅผ ์๊ตฌ์ ์ผ๋ก ์ ์ฅํ ์ ์์ด์. ๐
๐ฆ ์ฒ์ ์คํ ์ ๋๋กญ๋ฐ์ค ์ฐ๋ํ ๊ฑด์ง์ ๋ํ ํ๋ฉด์ด ๋น๋๋ค.
โ ์ฐ๋์ ์ฑ๊ณตํ๋ฉด ๋ฐฑ์ ํ๋ ๋ฐ์ดํฐ๊ฐ ๋ก๋๋ฉ๋๋ค. ๋ก๋๋๋ ๋์์๋ ํ๋ฉดํฐ์น๊ฐ ๋ถ๊ฐ๋ฅํฉ๋๋ค.
๐บ๐ธ ์ง์ญํ ๊ตฌํ์ด ๋์ด์์ด, ํ๊ธ ์ธ์๋ ์์ด๋ ์ง์ํ๋ ์ฑ์ ๋๋ค.
๐ ๋ผ์ดํธ๋ชจ๋, ๋คํฌ๋ชจ๋๋ฅผ ์ง์ํด์ !
๐ ๋ฉ๋ชจ๋ฅผ ์ถ๊ฐํ๊ฑฐ๋, ์ญ์ ํ ์ ์์ด์.
โจ ์ ๋ชฉ๊ณผ ๋ด์ฉ์ ๊ตฌ๋ถํด ํ ์คํธ ํฌ๊ธฐ๊ฐ ๋ณํํ๊ณ , ๊ทธ์๋ฐ๋ผ ์ข์ธก ์ ์ ๋ด์ฉ๋ ์ค์๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ๋์.
๐ ์์น๋ฐ๋ฅผ ์ด์ฉํด ์ํ๋ ๋ฉ๋ชจ๋ฅผ ๊ฒ์ํ ์ ์์ด์.
๐ ๋ฉ๋ชจ์ ๋ด์ฉ์ ๊ณต์ ํ ์๋ ์์ด์ !
UI | Local | Remote |
---|---|---|
UIKit | CoreData | SwifryDropbox |
์ํฉ
๋ฉ๋ชจ์ฅ์ linebreak๊ฐ 1๊ฐ์ผ ๋ Crash๊ฐ ๋๋ ํ์์ด ๋ํ๋ฌ๋ค. ์๋๋ ๋ชจ๋ ๋ฉ๋ชจ๋ฅผ ์ง์ ์ ๊ฒฝ์ฐ Crash๊ฐ ๋๋ ์ํฉ์ด๋ค.์ด์
๋ฐฐ์ด์ ์กฐํํ ๋ ์กด์ฌํ์ง ์๋ ์ธ๋ฑ์ค๋ฅผ ์กฐํํ ๊ฒฝ์ฐ ์ฑ์ด ์ฃฝ์ด๋ฒ๋ฆฌ๋ ์ํฉ์ด์๋ ๊ฒ์ด๋ค.ํด๊ฒฐ
๋ฐ๋ผ์ ์ธ๋ฑ์ค๋ฅผ ์์ ํ๊ฒ ์กฐํํ๋๋ก subscript๋ฅผ extension ํด์ฃผ์ด ์กฐํ๊ฐ ๋ถ๊ฐ๋ฅํ ์ํฉ์ ๋ง๊ฒ ๋์ฒํ ์ ์๋๋ก ํด๊ฒฐํ์๋ค.extension Collection { subscript (safe index: Index) -> Element? { return indices.contains(index) ? self[index] : nil } }
์ค๋ฅ๋ฉ์ธ์ง
- UIActivityViewController๋ฅผ present๋ฅผ ํด์ฃผ๋ ค๋๋ฐ ์๋์ ๊ฐ์ ์ค๋ฅ๋ฉ์ธ์ง๊ฐ ๋ด๋ค.
Thread 1: "Your application has presented a UIAlertController (<UIAlertController: 0x10d813a00>) of style UIAlertControllerStyleActionSheet from CloudNotes.SplitViewController (<CloudNotes.SplitViewController: 0x11f7068f0>).
The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover.
You must provide location information for this popover through the alert controller's popoverPresentationController.
You must provide either a sourceView and sourceRect or a barButtonItem.
If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation."
- ๊ฐ๋จํ ํด์ํ์๋ฉด iPad์์ ์ก์ ์ํธ๋ฅผ present๋ฅผ ํ ๊ฒฝ์ฐ ๋ชจ๋ฌ์คํ์ผ์ด UIModalPresentationPopover์ด๊ณ , ์ด๊ฑธ ์ฌ์ฉํ ๋๋ barButtonItem ๋๋ ํด๋น ์ฐฝ์ ๋ํ ์์น๋ฅผ ์ค์ ํด์ฃผ์ด์ผ ํ๋ค๊ณ ๋์ด์๋ค.
- ๋ฐ๋ผ์ ์ค์ ํด์ฃผ์ด์ผ ํ๋ ๊ฒ์ 2๊ฐ์ง์ค ํ๋์ด๋ค.
- ํ์์ ์ผ๋ก sourceView ์ง์ ํด์ฃผ๊ธฐ
- popoverPresentationController์ sourceRect ๋๋ barButtonItem ํ ๋นํด์ฃผ๊ธฐ
ํด๊ฒฐ ๋ฐฉ๋ฒ
์ผ๋ฟ์ present ํด์ฃผ๊ธฐ ์ ์ ๋ค์๊ณผ ๊ฐ์ if๋ฌธ์ ์ถ๊ฐํด์ฃผ์!
- UIBarButtonItem์ ์ถ๊ฐํด์ฃผ๋ ๋ฐฉ๋ฒ
// UIViewController extension ๋ด๋ถ...
if let popoverController = activityViewController.popoverPresentationController {
popoverController.sourceView = self.splitViewController?.view
popoverController.barButtonItem = sender // ๋ฉ์๋ ๋ด๋ถ๋ผ์ ํ๋ผ๋ฏธํฐ๋ก barButtonItem ์ ๋ฌ๋ฐ์ ํ ๋นํด์ฃผ์๋ค.
}
- ์์น๋ฅผ ์ ํด์ฃผ๋ ๋ฐฉ๋ฒ
if let popoverController = activityViewController.popoverPresentationController {
popoverController.sourceView = self.splitViewController?.view // presentํ ๋ทฐ ์ง์
popoverController.sourceRect = CGRect( // ๋ทฐ์ ์ ๊ฐ์ด๋ฐ ์์น๋ก ์ง์
x: self.splitViewController?.view.bounds.midX,
y: self.splitViewController?.view.bounds.midY,
width: 0,
height: 0
)
popoverController.permittedArrowDirections = [] // ํ์ดํ๋ฅผ ๋น๋ฐฐ์ด๋ก ๋์
}
์์น๋ฅผ ์ง์ ํ๊ณ ๋์ ํ๋ฉด์ ๋๋ ธ๋๋ฐ... ๊ฐ์ด๋ฐ ์์น์ ์์๊ณ ์์ํ ๊ณณ์ ์๋ค....
- ํด๊ฒฐ๋ฐฉ๋ฒ
- ํ๋ฉด์ด ๋์๊ฐ ๋๋ง๋ค ํฌ์ง์
์ ๋ค์ ์ก์์ฃผ๋ฉด ๋๋ค. ๊ทธ๊ฑธ ์ํด viewWillTransition ๋ฉ์๋๋ฅผ ํ์ฉํด๋ณด๊ฒ ๋ค.
- ์ด ๋ฉ์๋๋ ViewController์ ๋ทฐ ํฌ๊ธฐ๋ฅผ ๋ณ๊ฒฝํ๊ธฐ ์ ์ ํธ์ถ์ด ๋๋ค.
- ์ผ๋จ ์ผ๋ฟ์ presentํ๋ ๋ทฐ์ popoverController๋ผ๋ ๋ณ์๋ฅผ ๋ง๋ค์ด์ค๋ค.
class SplitViewController: UISplitViewController {
...
var popoverController: UIPopoverPresentationController?
- ๊ทธ๋ฆฌ๊ณ viewWillTransition ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ์ฌ ์์น๋ฅผ ๊ณ ์ณ์ฃผ๋ ๋ก์ง์ ์ถ๊ฐํ๋ค.
if let popoverController = self.popoverController {
popoverController.sourceRect = CGRect(
x: size.width * 0.5,
y: size.height * 0.5,
width: 0,
height: 0)
}
- UIViewController extension์ผ๋ก ๋ง๋ค์ด์ค ๋ฉ์๋ ๋ด๋ถ(๋งจ ์ฒ์ ์ผ๋ฟ์ ์์ฑํ์ฌ presentํ๋ ๊ณณ)์๋ popoverController๋ฅผ ํ ๋นํด์ฃผ๋๋ก ํด์ฃผ์๋ค.
let splitViewController = self.splitViewController as? SplitViewController
splitViewController?.popoverController = popoverController
UITableView์ selectRow๋ฅผ ํตํด Select๋ฅผ ์๋ํ์ ๋, ์๋์ ๊ฐ์ ์๋ฌ๊ฐ ๋๋ฉด์ Crash๊ฐ ๋ฐ์ํ๋ค.
Thread 1:
"Attempted to scroll the table view to an out-of-bounds row (0) when there are only 0 rows in section 0.
Table view: <UITableView: 0x13f031400;
frame = (0 0; 420 834);
clipsToBounds = YES;
autoresize = W+H; gestureRecognizers = <NSArray: 0x600000031680>;
layer = <CALayer: 0x600000ec7b80>; contentOffset: {0, -74};
contentSize: {420, 72.5}; adjustedContentInset: {74, 0, 20, 0};
dataSource: <CloudNotes.MemoListViewController: 0x14880fad0>>"
์ํฉ
๋ฉ๋ชจ์ฅ์ ๋ง์ง๋ง ๋จ์ ์ ์ ์ง์ฐ๊ฒ ๋๋ฉด์ selectRow๊ฐ ํธ์ถ์ด ๋๋ ์ํฉ์ด์๋ค.์ด์
์ ์ ์ง์ฐ๊ณ ๋ ํ๋๊น UITableView์ ๋ณด์ฌ์ค ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ์ง ์๊ณ , Cell๋ ์กด์ฌํ์ง ์๋ ์ํฉ์ด์๋๋ฐ,์กด์ฌํ์ง ์๋ ์
์Select
๋ฅผ ํ๋ ค๊ณ ํด์ ํฌ๋์ฌ๊ฐ ๋ ๊ฒ์ด์๋ค.ํด๊ฒฐ
๋ฐ๋ผ์ Select๋ฅผ ํ๊ธฐ ์ ์ ๋จผ์ UITableView์numberOfRows(inSection:)
๋ฉ์๋๋ฅผ ํตํด ํด๋น ๊ฐ์ด 0์ด ์๋ ๊ฒฝ์ฐ์๋ง seletRow๋ฅผ ํธ์ถํ ์ ์๋๋ก guard๋ฌธ์ ์ถ๊ฐํ์ฌ ํด๊ฒฐํด์ฃผ์๋ค.
func updateData(at index: Int) {
guard self.tableView.numberOfRows(inSection: .zero) != .zero else {
return
}
...
tableView.selectRow(at: IndexPath(row: .zero, section: .zero), animated: false, scrollPosition: .middle)
}
JSON ๋ชจ๋ธ์์ Core Data๋ก ๋ฆฌํฉํ ๋ง ๊ณผ์ ์์ ๋ ์๋ฌ์๋ค.
Thread 1:
"Invalid update: invalid number of rows in section 0.
The number of rows contained in an existing section after the update (15) must be equal to the number of rows contained in that section before the update (15), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
Table view:
<UITableView: 0x11081d400;
frame = (0 0; 420 1194);
clipsToBounds = YES;
autoresize = W+H;
gestureRecognizers = <NSArray: 0x6000033708a0>; layer = <CALayer: 0x600003d8cb40>;
contentOffset: {0, -74};
contentSize: {420, 1160};
adjustedContentInset: {74, 0, 20, 0};
dataSource: <CloudNotes.NotesViewController: 0x12a808ee0>>"
์ํฉ
ํ ์ด๋ธ ๋ทฐ์ ์น์ ์ ํ ๊ฐ์์ ์ค์ ๋ณด์ฌ์ค ์น์ ๊ฐ์๊ฐ ๋ง์ง ์์์ ๋ฐ์ํ๋ ์ค๋ฅ์ด๋ค.์ด์
ํ ์ด๋ธ๋ทฐ์ ์ ์ ์ญ์ ํ๋ฉด์ ํ ์ด๋ธ๋ทฐ์ ๋ณด์ฌ์ค ๋ฐ์ดํฐ๋ ๋์ผํ๊ฒ ์ญ์ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ด์ผ ํ๋๋ฐ, ๋๋ฝ๋์ ๋ฐ์ํ ๊ฒ์ด์๋ค.ํด๊ฒฐ
์ ์ ์ถ๊ฐ, ์ญ์ ํ ๋ ํ ์ด๋ธ๋ทฐ์ ๋ณด์ฌ์ค ์น์ ์ ๊ฐ์๋ ๋์ผํ ์ ์๋๋ก PersistentManager์ notes ๊ด๋ฆฌ(๋ฐฐ์ด ์์ ์ ๊ฑฐ, ์ฝ์ด๋ฐ์ดํฐ ์์ ์ ๊ฑฐ)๋ ๋นผ๋จน์ง ์๋๋ก ํด์ฃผ์๋ค.
๋๋์
Cell์ ๋ฉ์๋๋ฅผ ํตํด selectํ๊ฑฐ๋ insert, delete ๋ฑ๊ณผ ๊ฐ์ ์์ ์ ํ ๋์๋ ์์๊ฐ์ Crash๊ฐ ๋ฐ์ํ์ง ์๋๋ก ๋ฐ์ดํฐ์ Cell์ ์ฒ๋ฆฌ๋ฅผ ๊ผผ๊ผผํ ํ๋๋ก ์ ์ํด์ผ๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์๋ค.
๋ค์ด๋ก๋๊ฐ ๋๋ ํ CoreData๋ฅผ fetch๋ฅผ ํ๊ณ TableView๋ฅผ reload๋ฅผ ํด์ฃผ๊ณ ์ถ์์ผ๋ ์คํจํ์๋ค.
์ด์
ํ์ผ์ด ์ฌ๋ฌ๊ฐ๊ฐ ์กด์ฌํ์ฌ, ์ฌ๋ฌ๊ฐ์ ํ์ผ์ ๋ค์ด๋ก๋ ํ๊ธฐ ์ํด ๋ฐ๋ณต๋ฌธ์ ๋๋ฆฌ๊ณ ์์์ผ๋, fetch์ reload๋ฅผ for-in๋ฌธ ๋ด๋ถ์์ ํด์ฃผ๊ณ ์์ด์, ๋ทฐ๊ฐ ์ ๋ฐ์ดํธ ๋ ๋๊ฐ ์๊ณ , ์๋๊ธฐ๋ ํ๋ ํ์์ด ๋ํ๋ฌ๋ค.
* `ํด๊ฒฐ` ๊ทธ๋์ `for-in๋ฌธ์ด ์ข ๋ฃ๋ ์์ `์ `fetch`๋ฅผ ํ๊ณ view๋ฅผ reload๋ฅผ ํด์ฃผ๊ธฐ ์ํด, ๋ค์ด๋ก๋๊ฐ ๋ชจ๋ ์๋ฃ๋๋ ์์ ์ `DispatchGroup`๋ฅผ ํ์ฉํ์ฌ `์ถ์ `ํ๊ณ , ๋ฐ๋ณต๋ฌธ์์ ์์๋์๋ ๋ค์ด๋ก๋ ์์ ์ด ๋ชจ๋ ๋๋๊ฒ ๋๋ฉด ์๋ ๋ทฐ๋ฅผ ๋ค์ ์ค์ ํ๋๋ก ์ฝ๋๋ฅผ ์์ ํ์๋ค.
func download(_ tableViewController: NotesViewController?) {
let group = DispatchGroup() // ๊ทธ๋ฃน ์์ฑ
for fileName in fileNames {
let destURL = applicationSupportDirectoryURL.appendingPathComponent(fileName)
let destination: (URL, HTTPURLResponse) -> URL = { _, _ in
return destURL
}
group.enter() // ์์
์์
client?.files.download(path: fileName, overwrite: true, destination: destination)
.response { _, error in
if let error = error {
print(error)
}
group.leave() // ์์
๋
}
}
group.notify(queue: .main) { // ๋ชจ๋ ์์
์ด ๋๋๋ค๋ฉด ...
PersistentManager.shared.setUpNotes()
tableViewController?.tableView.reloadData()
tableViewController?.stopActivityIndicator()
}
}
- ์ต๊ทผ์ ์์ฑ, ์์ ํ์๋ ๋ฉ๋ชจ๊ฐ ์๋จ์ผ๋ก ์ฌ๋ผ์ฌ ์ ์๋๋ก ๋ฉ๋ชจ ๋ฆฌ์คํธ์ ์ ๋ ฌ์ ๋ ์ง๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ ๋ ฌ
- ์ด๋ค ๋ฉ๋ชจ๋ฅผ ์ ํํด์ ์์ฑํ๊ณ ์๋์ง ํ๋์ ๋ณด๊ธฐ ํธํ๋๋ก ์์ฑํ๊ณ ์๋ Cell์ ๊ณ์ Select ๋๋๋ก ๊ตฌํ
- ์์ฑํ๋ ๋์ค ๋ ์ง๊ฐ ์ ๋ฐ์ดํธ ๋๋ฉด, ์๋จ์ผ๋ก ์ด๋ํ๋ฉด์ Select๋ ์๋จ์ผ๋ก ์ด๋.
- ๋ฉ๋ชจ๋ฅผ ์ถ๊ฐํ์ ๋ ์ถ๊ฐํ ์๋ก์ด ๋ฉ๋ชจ๋ฅผ Select ๋๋๋ก ๊ตฌํ
- ๋ฉ๋ชจ๋ฅผ ์ญ์ ํ์ ๋, ์ญ์ ํ ๋ถ๋ถ ์ดํ ๋ฉ๋ชจ๋ฅผ ์๋์ผ๋ก Select ๋๋๋ก ๊ตฌํ
- ์ค์์ดํ ๋ฐ ๋๋ณด๊ธฐ ๋ฒํผ ํฐ์น์ ๋ณด์ฌ์ง๋ ์ก์ ๋ฒํผ์ด ๋จ์ ํ ์คํธ๊ฐ ์๋ ์์ด์ฝ์ด ํ๊ธฐ๋๋๋ก ๊ตฌํ
- Share๋ฅผ ํฐ์นํ์ฌ UIActivityViewController๊ฐ present ๋์์ ๋ ํ๋ฉด ํ์ ์์๋ ์ปจํธ๋กค๋ฌ๊ฐ ์ค์์ ๊ณ์ ์์นํ ์ ์๋๋ก ๊ตฌํ
- ๋ฉ๋ชจ์ CRUD๋ฅผ ๊ตฌํ ๋ฐ View์ ๋ณด์ฌ์ค ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ ์ ์๋
PersistentManager
๊ตฌํ - fetch๋ฅผ ํ ๋
Predicate
,Sort
๋ฑ์ ์ ์ฐํ๊ฒ ํ ์ ์๋๋ก ํ๋ผ๋ฏธํฐ ๋ณ๋ ๊ตฌํ
AttributtedString
์ ์ฌ์ฉํ์ฌ TextView์ ์ ๋ชฉ๊ณผ ๋ณธ๋ฌธ์ ํฐํธ๋ฅผ ๋ค๋ฅด๊ฒ ํ์ฌ ์ฌ์ฉ์๊ฐ ๋ณด๊ธฐ์ ํธํ๋๋ก ๊ตฌํ- textView์ delegate ๋ฉ์๋(
shouldChangeTextIn
)์ textView์typingAttributes
ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ ฅ์ค์๋ ์ ๋ชฉ๊ณผ ๋ณธ๋ฌธ์ ๋ง๋ ํฐํธ๊ฐ ์ ์ฉ๋๋๋ก ๊ตฌํ
- ์ ๋ก๋ ํ๋ ์์ ์ ์ ํ ๋ ๊ณ ๋ คํ ๊ฒ์ ๋๋ฌด ๋น๋ฒํ๊ฒ ์ ๋ก๋๋ฅผ ํ์ง ์์์ผ ํ๋ฉฐ, ์ฑ์ด ์๋์น ์๊ฒ ์ข ๋ฃ๋์ด๋ ์ด๋์ ๋ ๋ฐฉ์ด๊ฐ ๋์ด์ผ ํ๋ค๋ ์ ์ด์๋ค.
- ์ฒ์์๋ ํ ์คํธ๋ทฐ์ ๋ณํ๊ฐ ์ผ์ด๋ ๋๋ง๋ค ํ๋ ค๊ณ ํ์์ผ๋ ๋๋ฌด ๋น๋ฒํ๊ฒ ์ผ์ด๋ ๊ฒ์ผ๋ก ๋ณด์๋ค. ๊ทธ๋์ ์ข ๋ฃํ ๋ ํ๋ ค๊ณ ํ๋ ์๋์น ์์ ์ข ๋ฃ์ ์ ํ ๋ฐฉ์ด๊ฐ ๋์ง ์์๋ค.
- ์ฌ๋ฌ ๊ณ ๋ฏผ์ ํ ๊ฒฐ๊ณผ ํค๋ณด๋๋ฅผ ๋ด๋ฆด ๋ ๋ง๋ค ์ ๋ก๋๋ฅผ ํ๋๋ก ํ์ฌ ์ ๋ก๋๊ฐ ๋น๋ฒํ๊ฒ ์ผ์ด๋์ง ์๋๋ก ํ์๊ณ ์๋์น ์๊ฒ ์ข ๋ฃ๋์ด๋ ๋ฐฉ์ด๋ฅผ ํ ์ ์๋๋ก ํ์๋ค.
- ๋ค์ด๋ก๋ ์์ ์ ์ฒ์์ ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ ์์ ์ dropbox์ ๋ฐ์ดํฐ๋ฅผ ๋ค์ด๋ก๋ ํ์ฌ ๋ณด์ฌ์ฃผ๋ ์ฃผ๋๋ก ๊ตฌํํ๊ธธ ์ํ๋ค. ๊ทธ๋์ ์ธ์ฆ์ด ์๋ฃ ๋๊ณ authResult(์ธ์ฆ๊ฒฐ๊ณผ)๊ฐ success๊ฐ ๋๋ฉด download๋ฅผ ํ๋๋ก ํ์๋ค.
- ๋ํ, ๋ค์ด๋ก๋๋ ๋น๋๊ธฐ๋ก ์งํ์ด ๋๊ธฐ ๋๋ฌธ์ DispatchGroup์ ์ฌ์ฉํ์ฌ ๋ค์ด๋ก๋๊ฐ ์๋ฃ๋๋ฉด ์ฑ์ ๋ฐ์ดํฐ๋ฅผ ๋ฟ๋ ค์ฃผ๊ณ ํ ์ด๋ธ๋ทฐ๋ฅผ ์ ๋ฐ์ดํธ ํ๋๋ก ๊ตฌํํ์๋ค.
- ๋ค์ด๋ก๋๊ฐ ์งํ๋ ๋์ ๋ทฐ๋ ์๋ฌด๊ฒ๋ ๋ณด์ฌ์ฃผ์ง ์๊ฒ ๋๋ค.
- ๋ก๋ฉ์ค์ด๋ผ๋ ๊ฒ์ ์ฌ์ฉ์์๊ฒ ์๋ ค์ฃผ๊ธฐ ์ํด์ ActivityIndicator๋ฅผ ์ฌ์ฉํ์๋ค.
- ๋ค์ด๋ก๋๋ฅผ ์์ฒญํ๊ณ ActivityIndicator๋ฅผ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๋๋ก ํ๊ณ ๋ค์ด๋ก๋๊ฐ ๋ชจ๋ ์๋ฃ๋๋ฉด ActivityIndicator๋ ์ข ๋ฃ๋๋ฉฐ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๋๋ก ๊ตฌํํ์๋ค.
tableView.reloadData๋ก ํ ์ด๋ธ๋ทฐ์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ค๋ฉด ๋๋ฌด ๋นํจ์จ์ ์ด๋ผ๊ณ ์๊ฐํ์ฌ ์์ ๋ ๋ถ๋ถ๋ง ์ ๋ฐ์ดํธํ๋๋ก ๊ตฌํํ์๋ค.
- MemoListViewController์์ MemoDetailViewController๋ก ํ๋ฉด์ ํ๋ ๋ ํฐ์น๋ ํ ์ด๋ธ๋ทฐ์ ์ indexPath๋ฅผ ์ ๋ฌํ์ฌ ํ๋กํผํฐ๋ก ์ ์ฅํ๋ค.
- indexPath๋ฅผ SplitViewController๋ก ์ ๋ฌํ์ฌ SplitViewController๊ฐ ํ๋กํผํฐ๋ก ๊ฐ์ง๊ณ ์๋ primaryVC์ updateData ๋ฉ์๋๋ฅผ ์คํํ๋ค.
- MemoListViewController์์ ์ ๋ฌ๋ฐ์ indexPath๋ก ํด๋น๋๋ ์ ์ ๋ฐ์ดํฐ๋ง ์ ๋ฐ์ดํธํ๋ค.
extension MemoListViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let splitVC = self.splitViewController as? SplitViewController else {
return
}
splitVC.present(at: indexPath.row)
}
}
class SplitViewController: UISplitViewController {
func present(at indexPath: Int) {
let title = memoList[indexPath].title
let body = memoList[indexPath].body
secondaryVC.updateTextView(with: MemoDetailInfo(title: title, body: body))
secondaryVC.updateIndex(with: indexPath)
show(.secondary)
}
}
extension MemoDetailViewController: UITextViewDelegate {
private var currentIndex: Int = 0
func textViewDidChange(_ textView: UITextView) {
guard let splitVC = self.splitViewController as? SplitViewController else {
return
}
let memo = createMemoData(with: textView.text)
splitVC.updateMemoList(at: currentIndex, with: memo)
}
}
class SplitViewController: UISplitViewController {
func updateMemoList(at index: Int, with data: Memo) {
memoList[index] = data
let title = data.title.prefix(Constans.maximumTitleLength).description
let body = data.body.prefix(Constans.maximumBodyLength).description
let lastModified = data.lastModified.formattedDate
let memoListInfo = MemoListInfo(title: title, body: body, lastModified: lastModified)
primaryVC.updateData(at: index, with: memoListInfo)
}
}
class MemoListViewController: UITableViewController {
func updateData(at index: Int, with data: MemoListInfo) {
memoListInfo[index] = data
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none)
}
}
https://developer.apple.com/documentation/coredata/modeling_data/generating_code
ํ๋ก์ ํธ ํ๋ ๋์ค์ codegen์ ์ด๋ค ์ต์ ์ผ๋ก ์ค์ผํ ์ง ๊ฐ์ด ์์์ ์ฐพ์๋ณด์๋ค.
- ํด๋น entity์ ๋ํ ํด๋์ค ์ ์ธ์ ์๋์ผ๋ก ๋ง๋ค์ด ์ฃผ๋ ์ต์
์ ์ค์ ํฉ๋๋ค.
- None/Manual: ๊ด๋ จ ํ์ผ์ ์๋์ผ๋ก ๋ง๋ค์ด์ฃผ์ง ์๋๋ค. ๊ฐ๋ฐ์๋ DataModel์ ์ ํํ ์ํ์์ Editor-Create NSManagedObject Subclass ํญ๋ชฉ์ ํด๋ฆญํ์ฌ ํด๋์ค ์ ์ธ ํ์ผ๊ณผ ํ๋กํผํฐ extension ํ์ผ์ ๋น๋์๋ง๋ค ์ถ๊ฐ์์ผ ์ฃผ๊ณ , ์ด๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํด์ผ ํ๋ค.
- Class Definition: ํด๋์ค ์ ์ธ ํ์ผ๊ณผ ํ๋กํผํฐ ๊ด๋ จ extension ํ์ผ์ ๋น๋์๋ง๋ค ์๋์ผ๋ก ์ถ๊ฐ์์ผ์ค๋ค. ๋ฐ๋ผ์ ๊ด๋ จ๋ ํ์ผ์ ์ ํ ์ถ๊ฐ์์ผ์ค ํ์๊ฐ ์๋ค.(๊ทธ๋์๋ ์๋๋ค. ๋ง์ฝ ์๋์ผ๋ก ์ถ๊ฐ์์ผ์ค ์ํ์์ ๋น๋๋ฅผ ์๋ํ๋ฉด ์ปดํ์ผ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.)
- Category/Extension: ํ๋กํผํฐ ๊ด๋ จ extensionํ์ผ๋ง ์๋์ผ๋ก ์ถ๊ฐ์์ผ ์ค๋ค. ์ฆ, ํด๋์ค ์ ์ธ์๋ ์ฌ์ฉ์๊ฐ ์ํ๋ ๋ก์ง์ ์์ ๋กญ๊ฒ ์ถ๊ฐํ ์ ์๋ค.
- Accessibility Inspector๋ฅผ ํ์ฉํ์ฌ ์ ๊ทผ์ฑ์ ์ํด Run Audit์ ํตํด ๊ฐ์ ํ ํญ๋ชฉ๋ค์ด ์๋์ง ๊ฒ์ํ์๋ค.
- ๊ทธ๋ฆฌ๊ณ VoiceOver๋ฅผ ์ง์ ์คํํด์ ํ ์คํธํด๋ณด๋ฉฐ ๋ถ์กฑํ ๋ถ๋ถ์ด ์๋์ง ํ์ธํด๋ณด์๋ค.
- ๋ค์ด๋๋ฏน ํ์ ์ ๊ฒฝ์ฐ๋ ํ ์คํธ ํฌ๊ธฐ๊ฐ ์ ์ฐํ์ง ๊ฒ์ํ์๋ค.
[ํ์ต ๊ธฐ๋ก ํ์ ]
- STEP 1 : ๋ฆฌ์คํธ ๋ฐ ๋ฉ๋ชจ์์ญ ํ๋ฉด UI๊ตฌํ
- STEP 2 : ์ฝ์ด๋ฐ์ดํฐ DB ๊ตฌํ
- STEP 3 : ํด๋ผ์ฐ๋ ์ฐ๋
- STEP 4 : ์ถ๊ฐ ๊ธฐ๋ฅ ๋ฐ UI ๊ตฌํ
๋ฆฌ์คํธ ํ๋ฉด๊ณผ ๋ฉ๋ชจ์์ญ ํ๋ฉด์ SplitViewController๋ฅผ ํ์ฉํ์ฌ ๊ตฌํํฉ๋๋ค.
- NotificationCenter๋ฅผ ํ์ฉํ์ฌ ํค๋ณด๋๊ฐ ํ๋ฉด์ ํ์๋ ๋ UITextView๋ ํค๋ณด๋์ ๋์ด๋งํผ contentInset์ ์กฐ์ ํ๋๋ก ๊ตฌํํ์๋ค.
- ํธ์ง์ ๋๋ธ ํ ๋ค๋ฅธ ๋ฉ๋ชจ๋ฅผ ๋๋ ์ ๋ ํธ์ง๋ชจ๋๋ฅผ ์ข ๋ฃํ ์ ์๋๋ก ๊ตฌํํ๋ค. UITapGestureRecognizer๋ฅผ ํ์ฉํ์ฌ ์ฌ์ฉ์๊ฐ ํ ์คํธ๋ทฐ๊ฐ ์๋ ๋ค๋ฅธ ๋ถ๋ถ์ ํฐ์นํ์ ๋ endEditing ๋ฉ์๋๋ฅผ ํธ์ถํ๋๋ก ํ์๋ค.
- ์ค์๊ฐ ๋ฐ์์ ์ํด UITextViewDelegate๋ฅผ ํ์ฉํ์ฌ UITextView๊ฐ ์์ ๋ ๋ ๋ง๋ค ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ณ , UITableView๋ ์ ๋ฐ์ดํธํ๋๋ก ๊ธฐ๋ฅ์ ๊ตฌํํ์๋ค.
- ์กด์ฌํ์ง์๋ ์ธ๋ฑ์ค๋ฅผ ์กฐํํ์ ๋ Crash๊ฐ ๋์ง ์๋๋ก subscript๋ฅผ ํ์ฉํ์ฌ Crash๊ฐ ๋ฐ์ํ์ง ์๋๋ก ๊ตฌํํ์๋ค.
extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
- UILabel, UITextView์ ์ค์๊ฐ์ผ๋ก ๊ธ์จ ํฌ๊ธฐ๋ฅผ ์กฐ์ ํ ์ ์๋๋ก ๋ค์ด๋๋ฏน ํฐํธ ์ค์ ๋ฐ Automatically Adjusts Font ๊ธฐ๋ฅ์ ํ์ฑํ ํด์ฃผ์๋ค.
- MemoListViewController์ UITableViewDelegate ๋ฉ์๋ didSelectRowAt์์ SplitViewController์ present๋ฉ์๋๋ฅผ ํตํด ๋๋ฆฐ ํ ์ด๋ธ๋ทฐ ์ ์ indexPath๋ฅผ ํ์ฉํ์๋ค.
- indexPath๋ก SplitViewController๊ฐ ๊ฐ์ง๊ณ ์๋ Memo ๋ฐฐ์ด ํ์ ์ ๋ฐ์ดํฐ ์ค์์ ํด๋น๋๋ ๋ฐ์ดํฐ๋ฅผ ๊ณจ๋ผ์ MemoDetailViewController์ text view๋ฅผ ์ ๋ฐ์ดํธํ๋ค.
translatesAutoresizingMaskIntoConstraints
๋ ์false
๋ก ์ง์ ํด์ฃผ๋ ๊ฑธ๊น?- ํน์ ํ์ ํด๋น๋๋ ์ ์ ์ ๋ฐ์ดํธ ํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์์๊น?
GestureRecognizer
๋ฅผ ๋ฑ๋กํ์ ๋UITableViewDelagate
๊ฐ ์ ๋จนํต์ด์ง?- ์คํ ์ primaryVC์ด ๋ณด์ฌ์ก์ผ๋ฉด ์ข๊ฒ ๋๋ฐ...
- SplitViewController์ secondaryVC์ ์ ๋ฐฐ๊ฒฝ์์ด ํ์์ด์ง?
- ๋ฐ์ดํฐ๋ฅผ primary์ secondary์ ํจ์จ์ ์ผ๋ก ๋ฟ๋ ค์ค ์ ์์๊น?
์ํฉ
GestureRecognizer๋ฅผ ViewController์ ์ถ๊ฐํ์ UITableView์ Select๊ฐ ๋์ง ์๋ ํ์์ด ๋ํ๋ฌ๋ค.์ด์
๋ฑ๋กํ GestureRecognizer์ ํ๋กํผํฐ์ธ cancelsTouchesInView๊ฐ ๊ธฐ๋ณธ๊ฐ์ผ๋ก true๋ก ์ค์ ๋์ด์์ด ๋ฌธ์ ์๋ค.cancelsTouchesInVie
w๊ฐtrue
์ธ ๊ฒฝ์ฐ์๋ ์ ์ค์ฒ๋ฅผ ์ธ์ํ ํ์ ๋๋จธ์ง ํฐ์น์ ๋ณด๋ค์ ๋ทฐ๋ก ์ ๋ฌํ์ง ์๊ณ ์ทจ์๋์๊ธฐ ๋๋ฌธ์ UITableView์ Select๊ฐ ๋จน์ง ์์๋ ๊ฒ์ด๋ค.ํด๊ฒฐ
๋ฐ๋ผ์ cancelsTouchesInView๊ฐ์false
๋ก ํ ๋นํด์ค์ผ๋ก์จ ํด๋น ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์๋ค. ์ ์ค์ฒ๋ฅผ ์ธ์ํ ํ์๋ Gesture Recognizer์ ํจํด๊ณผ๋ ๋ฌด๊ดํ๊ฒ ํฐ์น ์ ๋ณด๋ค์ ๋ทฐ์ ์ ๋ฌํ ์ ์๊ฒ ๋์๋ค.
์ํฉ
๋ฉ๋ชจ์ฅ์ linebreak๊ฐ 1๊ฐ์ผ ๋ Crash๊ฐ ๋๋ ํ์์ด ๋ํ๋ฌ๋ค. ์๋๋ ๋ชจ๋ ๋ฉ๋ชจ๋ฅผ ์ง์ ์ ๊ฒฝ์ฐ Crash๊ฐ ๋๋ ์ํฉ์ด๋ค.์ด์
๋ฐฐ์ด์ ์กฐํํ ๋ ์กด์ฌํ์ง ์๋ ์ธ๋ฑ์ค๋ฅผ ์กฐํํ ๊ฒฝ์ฐ ์ฑ์ด ์ฃฝ์ด๋ฒ๋ฆฌ๋ ์ํฉ์ด์๋ ๊ฒ์ด๋ค.ํด๊ฒฐ
๋ฐ๋ผ์ ์ธ๋ฑ์ค๋ฅผ ์์ ํ๊ฒ ์กฐํํ๋๋ก subscript๋ฅผ extension ํด์ฃผ์ด ์กฐํ๊ฐ ๋ถ๊ฐ๋ฅํ ์ํฉ์ ๋ง๊ฒ ๋์ฒํ ์ ์๋๋ก ํด๊ฒฐํ์๋ค.extension Collection { subscript (safe index: Index) -> Element? { return indices.contains(index) ? self[index] : nil } }
Split View์์ ์ธํฐํ์ด์ค๊ฐ ์ถ์๋์์๋ ๋จผ์ ๋ณด์ฌ์ง๋ ๋ทฐ๋ฅผ secondary๊ฐ ์๋๋ผ primary๋ก ์ค์ ํ๊ธฐ
Split View์์ ์ธํฐํ์ด์ค๊ฐ ์ถ์๋์์๋ ๋จผ์ ๋ณด์ฌ์ง๋ ๋ทฐ๋ฅผ secondary๊ฐ ์๋๋ผ primary๋ก ์ค์ ํ๊ธฐ
- ์์ดํจ๋์์ ์คํ๋ฆฟ๋ทฐ๋ก ๋ค๋ฅธ ์ฑ๊ณผ ํ๋ฉด์ ๊ฐ์ด ์ฐ๋ ๊ฒฝ์ฐ ํ๋ฉด์ด ์์์ ธ์ primary์ secondary๋ทฐ๊ฐ ํ๋ฒ์ ๋ณด์ด์ง ์์๋ค. primary๋ทฐ์ธ ๋ฉ๋ชจ๋ชฉ๋ก์ด ๋จผ์ ๋ณด์ฌ์ง๊ฒ ํ๊ณ ์ถ์๋๋ฐ secondary๋ทฐ์ธ ๋ฉ๋ชจ์ฅ์ด ๋จผ์ ๋ณด์ฌ์ง๋ ํ์์ด ๋ฐ์ํ์๋ค.
- ๋ํดํธ ๊ฐ์ด secondary๋ทฐ์์ ํ์ธํ๊ณ primary๊ฐ ๋จผ์ ๋ณด์ฌ์ง๋๋ก delegate ๋ฉ์๋๋ฅผ ํตํด ์ค์ ํด์ฃผ์๋ค.
extension SplitViewController: UISplitViewControllerDelegate {
func splitViewController(
_ svc: UISplitViewController,
topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column
) -> UISplitViewController.Column {
return .primary
}
}
DateFormatter ์ง์ญํ
- TimeInterval ํ์ ์ผ๋ก ์ฃผ์ด์ง ๋ฉ๋ชจ ์์ฑ๋ ์ง๋ฅผ ๋ ์ง ํ์์ผ๋ก ๋ณ๊ฒฝํ๊ธฐ์ํด TimeInterval ํ์ ์ extensionํ์ฌ ์ฐ์ฐ ํ๋กํผํฐ๋ฅผ ๊ตฌํํ์๋ค.
- ์ฌ์ฉ์์ ์ง์ญ์ ๋ง๋ ๋ ์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด DateFormatter์ locale๋ฅผ ํ์ฉํ์๋ค.
extension TimeInterval {
var formattedDate: String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
let localeID = Locale.preferredLanguages.first
let deviceLocale = Locale(identifier: localeID ?? "ko-kr").languageCode
dateFormatter.locale = Locale(identifier: deviceLocale ?? "ko-kr")
dateFormatter.timeZone = TimeZone.current
return dateFormatter.string(from: Date(timeIntervalSince1970: self))
}
}
์ฝ๋๋ก ๋ทฐ ๊ตฌํํ๊ธฐ: SceneDelegate ์์ initial View Controller ์ค์
- ์คํ ๋ฆฌ๋ณด๋๋ฅผ ์ง์ด ํ SceneDelegate์ scene๋ฉ์๋์์ window์ rootViewController๋ฅผ ์ฑ์ ์ฒซํ๋ฉด์ ๋ณด์ด๋ splitVC๋ก ์ค์ ํ๋ค.
- ๊ทธ๋ฆฌ๊ณ makeKeyAndVisible()๋ก ํ๋ฉด์ ๋ณด์ด๋๋ก ์ค์ ํ์ฌ Storyboard์์ initial view controller๋ก ์ง์ ํ๋ ๊ฒ์ ๋์ ํด์ค ์ ์๋ค.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windoewScene = (scene as? UIWindowScene) else {
return
}
window = UIWindow(windowScene: windoewScene)
let splitVC = SplitViewController(style: .doubleColumn)
window?.rootViewController = splitVC
window?.makeKeyAndVisible()
}
BarButtonItem ํ์ฉ
- UIViewController์ ์๋
navigationItem
ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ์ฌ title๊ณผ BarButtonItem ๋ฑ navigation์ ํ์ํ item์ ์ค์ ํ ์ ์๋ค. UIBarButtonItem
์ ์ด๋์ ๋ผ์ด์ ์๋ image๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๊ฑฐ๋, barButtonSystemItem์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ ์ ์์ด ํ์ํ ๊ฒ์ ๊ณจ๋ผ์ ์ฌ์ฉํ ์ ์๋ค.
navigationItem.title = "๋ฉ๋ชจ"
navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .add,
target: self,
action: nil
)
navigationItem.rightBarButtonItem = UIBarButtonItem(
image: UIImage(systemName: "ellipsis.circle"),
style: .plain,
target: self,
action: nil
)
UISplitViewController
setViewController(_:for:)
: UISplitViewController์ ๋ฉ์๋๋ก Double Column ์คํ์ผ์ธ ๊ฒฝ์ฐ์ primary์ secondary ๋ทฐ์ปจํธ๋กค๋ฌ๋ฅผ ์ง์ ํ๋ค.- ์ด ๋ฉ์๋๋ก ์ง์ ํ๋ ๊ฒฝ์ฐ์ ์๋์ผ๋ก ๋ทฐ์ปจํธ๋กค๋ฌ์ ๋ค๋น๊ฒ์ด์ ์ปจํธ๋กค๋ฌ๋ฅผ ๊ฐ์ธ์ UISplitViewController์ ํ ๋นํด์ค๋ค.
class SplitViewController: UISplitViewController {
private let primaryVC = MemoListViewController(style: .insetGrouped)
private let secondaryVC = MemoDetailViewController()
private func setUpChildView() {
setViewController(primaryVC, for: .primary)
setViewController(secondaryVC, for: .secondary)
}
}
UITableView reloadRows ๋ฅผ ํ์ฉํด ์์ ๋ row๋ง ์ ๋ฐ์ดํธํ๊ธฐ
tableView.reloadData๋ก ํ ์ด๋ธ๋ทฐ์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ค๋ฉด ๋๋ฌด ๋นํจ์จ์ ์ด๋ผ๊ณ ์๊ฐํ์ฌ ์์ ๋ ๋ถ๋ถ๋ง ์ ๋ฐ์ดํธํ๋๋ก ๊ตฌํํ์๋ค.
- MemoListViewController์์ MemoDetailViewController๋ก ํ๋ฉด์ ํ๋ ๋ ํฐ์น๋ ํ ์ด๋ธ๋ทฐ์ ์ indexPath๋ฅผ ์ ๋ฌํ์ฌ ํ๋กํผํฐ๋ก ์ ์ฅํ๋ค.
- indexPath๋ฅผ SplitViewController๋ก ์ ๋ฌํ์ฌ SplitViewController๊ฐ ํ๋กํผํฐ๋ก ๊ฐ์ง๊ณ ์๋ primaryVC์ updateData ๋ฉ์๋๋ฅผ ์คํํ๋ค.
- MemoListViewController์์ ์ ๋ฌ๋ฐ์ indexPath๋ก ํด๋น๋๋ ์ ์ ๋ฐ์ดํฐ๋ง ์ ๋ฐ์ดํธํ๋ค.
extension MemoListViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let splitVC = self.splitViewController as? SplitViewController else {
return
}
splitVC.present(at: indexPath.row)
}
}
class SplitViewController: UISplitViewController {
func present(at indexPath: Int) {
let title = memoList[indexPath].title
let body = memoList[indexPath].body
secondaryVC.updateTextView(with: MemoDetailInfo(title: title, body: body))
secondaryVC.updateIndex(with: indexPath)
show(.secondary)
}
}
extension MemoDetailViewController: UITextViewDelegate {
private var currentIndex: Int = 0
func textViewDidChange(_ textView: UITextView) {
guard let splitVC = self.splitViewController as? SplitViewController else {
return
}
let memo = createMemoData(with: textView.text)
splitVC.updateMemoList(at: currentIndex, with: memo)
}
}
class SplitViewController: UISplitViewController {
func updateMemoList(at index: Int, with data: Memo) {
memoList[index] = data
let title = data.title.prefix(Constans.maximumTitleLength).description
let body = data.body.prefix(Constans.maximumBodyLength).description
let lastModified = data.lastModified.formattedDate
let memoListInfo = MemoListInfo(title: title, body: body, lastModified: lastModified)
primaryVC.updateData(at: index, with: memoListInfo)
}
}
class MemoListViewController: UITableViewController {
func updateData(at index: Int, with data: MemoListInfo) {
memoListInfo[index] = data
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none)
}
}
- View์ ๋ณด์ฌ์ค ์์๋ค์ ๋ณ๋์ ํ์ ์ผ๋ก ๋ง๋ค์ด ๋ณด์ฌ์ฃผ์๋ ๋ถ๋ถ์ ์ ๊ฑฐํ Core Data๋ก ๋ชจ๋ ํต์ผํ์ฌ ๋ฆฌํฉํ ๋ง
๋ฉ๋ชจ๋ฅผ ์ํ ์ฝ์ด๋ฐ์ดํฐ ๋ชจ๋ธ์ ์์ฑํฉ๋๋ค.
- ์ต๊ทผ์ ์์ฑ, ์์ ํ์๋ ๋ฉ๋ชจ๊ฐ ์๋จ์ผ๋ก ์ฌ๋ผ์ฌ ์ ์๋๋ก ๋ฉ๋ชจ ๋ฆฌ์คํธ์ ์ ๋ ฌ์ ๋ ์ง๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ ๋ ฌ
- ์ด๋ค ๋ฉ๋ชจ๋ฅผ ์ ํํด์ ์์ฑํ๊ณ ์๋์ง ํ๋์ ๋ณด๊ธฐ ํธํ๋๋ก ์์ฑํ๊ณ ์๋ Cell์ ๊ณ์ Select ๋๋๋ก ๊ตฌํ
- ์์ฑํ๋ ๋์ค ๋ ์ง๊ฐ ์ ๋ฐ์ดํธ ๋๋ฉด, ์๋จ์ผ๋ก ์ด๋ํ๋ฉด์ Select๋ ์๋จ์ผ๋ก ์ด๋.
- ๋ฉ๋ชจ๋ฅผ ์ถ๊ฐํ์ ๋ ์ถ๊ฐํ ์๋ก์ด ๋ฉ๋ชจ๋ฅผ Select ๋๋๋ก ๊ตฌํ
- ๋ฉ๋ชจ๋ฅผ ์ญ์ ํ์ ๋, ์ญ์ ํ ๋ถ๋ถ ์ดํ ๋ฉ๋ชจ๋ฅผ ์๋์ผ๋ก Select ๋๋๋ก ๊ตฌํ
- ์ค์์ดํ ๋ฐ ๋๋ณด๊ธฐ ๋ฒํผ ํฐ์น์ ๋ณด์ฌ์ง๋ ์ก์ ๋ฒํผ์ด ๋จ์ ํ ์คํธ๊ฐ ์๋ ์์ด์ฝ์ด ํ๊ธฐ๋๋๋ก ๊ตฌํ
- Share๋ฅผ ํฐ์นํ์ฌ UIActivityViewController๊ฐ present ๋์์ ๋ ํ๋ฉด ํ์ ์์๋ ์ปจํธ๋กค๋ฌ๊ฐ ์ค์์ ๊ณ์ ์์นํ ์ ์๋๋ก ๊ตฌํ
- ๋ฉ๋ชจ์ CRUD๋ฅผ ๊ตฌํ ๋ฐ View์ ๋ณด์ฌ์ค ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ ์ ์๋
PersistentManager
๊ตฌํ - fetch๋ฅผ ํ ๋
Predicate
,Sort
๋ฑ์ ์ ์ฐํ๊ฒ ํ ์ ์๋๋ก ํ๋ผ๋ฏธํฐ ๋ณ๋ ๊ตฌํ
AttributtedString
์ ์ฌ์ฉํ์ฌ TextView์ ์ ๋ชฉ๊ณผ ๋ณธ๋ฌธ์ ํฐํธ๋ฅผ ๋ค๋ฅด๊ฒ ํ์ฌ ์ฌ์ฉ์๊ฐ ๋ณด๊ธฐ์ ํธํ๋๋ก ๊ตฌํ- textView์ delegate ๋ฉ์๋(
shouldChangeTextIn
)์ textView์typingAttributes
ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ ฅ์ค์๋ ์ ๋ชฉ๊ณผ ๋ณธ๋ฌธ์ ๋ง๋ ํฐํธ๊ฐ ์ ์ฉ๋๋๋ก ๊ตฌํ
- Core Data - codegen์ ์ด๋ป๊ฒ ์ค์ ํด์ผ ์ ์ ํ ๊น?
- NSFetchRequest - returnsObjectsAsFaults ์์ฑ๊ฐ์ ์ด๋ค ์ญํ ์ ํ๋ ๊ฒ์ผ๊น?
- NSFetchRequestResult ํ๋กํ ์ฝ์ด ๋ญ๊น?
- ๋ณธ๋ฌธ์ ์ ๋ชฉ์ ๊ตต๊ฒ ํ์ํ๋ฉด์ ๋ค์ด๋๋ฏน ํ์ ์ ์ ์ฉํ ์ ์์๊น?
- UIContextualAction์ handler์ completeHandeler๋ ์ด๋ค ์ญํ ์ ํ๋ ๊ฒ์ผ๊น?
- UIAlertController - ActionSheet๋ฅผ iPad์์ present ํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ์ง?
์ค๋ฅ๋ฉ์ธ์ง
- UIActivityViewController๋ฅผ present๋ฅผ ํด์ฃผ๋ ค๋๋ฐ ์๋์ ๊ฐ์ ์ค๋ฅ๋ฉ์ธ์ง๊ฐ ๋ด๋ค.
Thread 1: "Your application has presented a UIAlertController (<UIAlertController: 0x10d813a00>) of style UIAlertControllerStyleActionSheet from CloudNotes.SplitViewController (<CloudNotes.SplitViewController: 0x11f7068f0>).
The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover.
You must provide location information for this popover through the alert controller's popoverPresentationController.
You must provide either a sourceView and sourceRect or a barButtonItem.
If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation."
- ๊ฐ๋จํ ํด์ํ์๋ฉด iPad์์ ์ก์ ์ํธ๋ฅผ present๋ฅผ ํ ๊ฒฝ์ฐ ๋ชจ๋ฌ์คํ์ผ์ด UIModalPresentationPopover์ด๊ณ , ์ด๊ฑธ ์ฌ์ฉํ ๋๋ barButtonItem ๋๋ ํด๋น ์ฐฝ์ ๋ํ ์์น๋ฅผ ์ค์ ํด์ฃผ์ด์ผ ํ๋ค๊ณ ๋์ด์๋ค.
- ๋ฐ๋ผ์ ์ค์ ํด์ฃผ์ด์ผ ํ๋ ๊ฒ์ 2๊ฐ์ง์ค ํ๋์ด๋ค.
- ํ์์ ์ผ๋ก sourceView ์ง์ ํด์ฃผ๊ธฐ
- popoverPresentationController์ sourceRect ๋๋ barButtonItem ํ ๋นํด์ฃผ๊ธฐ
ํด๊ฒฐ ๋ฐฉ๋ฒ
์ผ๋ฟ์ present ํด์ฃผ๊ธฐ ์ ์ ๋ค์๊ณผ ๊ฐ์ if๋ฌธ์ ์ถ๊ฐํด์ฃผ์!
- UIBarButtonItem์ ์ถ๊ฐํด์ฃผ๋ ๋ฐฉ๋ฒ
// UIViewController extension ๋ด๋ถ...
if let popoverController = activityViewController.popoverPresentationController {
popoverController.sourceView = self.splitViewController?.view
popoverController.barButtonItem = sender // ๋ฉ์๋ ๋ด๋ถ๋ผ์ ํ๋ผ๋ฏธํฐ๋ก barButtonItem ์ ๋ฌ๋ฐ์ ํ ๋นํด์ฃผ์๋ค.
}
- ์์น๋ฅผ ์ ํด์ฃผ๋ ๋ฐฉ๋ฒ
if let popoverController = activityViewController.popoverPresentationController {
popoverController.sourceView = self.splitViewController?.view // presentํ ๋ทฐ ์ง์
popoverController.sourceRect = CGRect( // ๋ทฐ์ ์ ๊ฐ์ด๋ฐ ์์น๋ก ์ง์
x: self.splitViewController?.view.bounds.midX,
y: self.splitViewController?.view.bounds.midY,
width: 0,
height: 0
)
popoverController.permittedArrowDirections = [] // ํ์ดํ๋ฅผ ๋น๋ฐฐ์ด๋ก ๋์
}
์์น๋ฅผ ์ง์ ํ๊ณ ๋์ ํ๋ฉด์ ๋๋ ธ๋๋ฐ... ๊ฐ์ด๋ฐ ์์น์ ์์๊ณ ์์ํ ๊ณณ์ ์๋ค....
- ํด๊ฒฐ๋ฐฉ๋ฒ
- ํ๋ฉด์ด ๋์๊ฐ ๋๋ง๋ค ํฌ์ง์
์ ๋ค์ ์ก์์ฃผ๋ฉด ๋๋ค. ๊ทธ๊ฑธ ์ํด viewWillTransition ๋ฉ์๋๋ฅผ ํ์ฉํด๋ณด๊ฒ ๋ค.
- ์ด ๋ฉ์๋๋ ViewController์ ๋ทฐ ํฌ๊ธฐ๋ฅผ ๋ณ๊ฒฝํ๊ธฐ ์ ์ ํธ์ถ์ด ๋๋ค.
- ์ผ๋จ ์ผ๋ฟ์ presentํ๋ ๋ทฐ์ popoverController๋ผ๋ ๋ณ์๋ฅผ ๋ง๋ค์ด์ค๋ค.
class SplitViewController: UISplitViewController {
...
var popoverController: UIPopoverPresentationController?
- ๊ทธ๋ฆฌ๊ณ viewWillTransition ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ์ฌ ์์น๋ฅผ ๊ณ ์ณ์ฃผ๋ ๋ก์ง์ ์ถ๊ฐํ๋ค.
if let popoverController = self.popoverController {
popoverController.sourceRect = CGRect(
x: size.width * 0.5,
y: size.height * 0.5,
width: 0,
height: 0)
}
- UIViewController extension์ผ๋ก ๋ง๋ค์ด์ค ๋ฉ์๋ ๋ด๋ถ(๋งจ ์ฒ์ ์ผ๋ฟ์ ์์ฑํ์ฌ presentํ๋ ๊ณณ)์๋ popoverController๋ฅผ ํ ๋นํด์ฃผ๋๋ก ํด์ฃผ์๋ค.
let splitViewController = self.splitViewController as? SplitViewController
splitViewController?.popoverController = popoverController
UITableView์ selectRow๋ฅผ ํตํด Select๋ฅผ ์๋ํ์ ๋, ์๋์ ๊ฐ์ ์๋ฌ๊ฐ ๋๋ฉด์ Crash๊ฐ ๋ฐ์ํ๋ค.
Thread 1:
"Attempted to scroll the table view to an out-of-bounds row (0) when there are only 0 rows in section 0.
Table view: <UITableView: 0x13f031400;
frame = (0 0; 420 834);
clipsToBounds = YES;
autoresize = W+H; gestureRecognizers = <NSArray: 0x600000031680>;
layer = <CALayer: 0x600000ec7b80>; contentOffset: {0, -74};
contentSize: {420, 72.5}; adjustedContentInset: {74, 0, 20, 0};
dataSource: <CloudNotes.MemoListViewController: 0x14880fad0>>"
์ํฉ
๋ฉ๋ชจ์ฅ์ ๋ง์ง๋ง ๋จ์ ์ ์ ์ง์ฐ๊ฒ ๋๋ฉด์ selectRow๊ฐ ํธ์ถ์ด ๋๋ ์ํฉ์ด์๋ค.์ด์
์ ์ ์ง์ฐ๊ณ ๋ ํ๋๊น UITableView์ ๋ณด์ฌ์ค ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ์ง ์๊ณ , Cell๋ ์กด์ฌํ์ง ์๋ ์ํฉ์ด์๋๋ฐ,์กด์ฌํ์ง ์๋ ์
์Select
๋ฅผ ํ๋ ค๊ณ ํด์ ํฌ๋์ฌ๊ฐ ๋ ๊ฒ์ด์๋ค.ํด๊ฒฐ
๋ฐ๋ผ์ Select๋ฅผ ํ๊ธฐ ์ ์ ๋จผ์ UITableView์numberOfRows(inSection:)
๋ฉ์๋๋ฅผ ํตํด ํด๋น ๊ฐ์ด 0์ด ์๋ ๊ฒฝ์ฐ์๋ง seletRow๋ฅผ ํธ์ถํ ์ ์๋๋ก guard๋ฌธ์ ์ถ๊ฐํ์ฌ ํด๊ฒฐํด์ฃผ์๋ค.
func updateData(at index: Int) {
guard self.tableView.numberOfRows(inSection: .zero) != .zero else {
return
}
...
tableView.selectRow(at: IndexPath(row: .zero, section: .zero), animated: false, scrollPosition: .middle)
}
JSON ๋ชจ๋ธ์์ Core Data๋ก ๋ฆฌํฉํ ๋ง ๊ณผ์ ์์ ๋ ์๋ฌ์๋ค.
Thread 1:
"Invalid update: invalid number of rows in section 0.
The number of rows contained in an existing section after the update (15) must be equal to the number of rows contained in that section before the update (15), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
Table view:
<UITableView: 0x11081d400;
frame = (0 0; 420 1194);
clipsToBounds = YES;
autoresize = W+H;
gestureRecognizers = <NSArray: 0x6000033708a0>; layer = <CALayer: 0x600003d8cb40>;
contentOffset: {0, -74};
contentSize: {420, 1160};
adjustedContentInset: {74, 0, 20, 0};
dataSource: <CloudNotes.NotesViewController: 0x12a808ee0>>"
์ํฉ
ํ ์ด๋ธ ๋ทฐ์ ์น์ ์ ํ ๊ฐ์์ ์ค์ ๋ณด์ฌ์ค ์น์ ๊ฐ์๊ฐ ๋ง์ง ์์์ ๋ฐ์ํ๋ ์ค๋ฅ์ด๋ค.์ด์
ํ ์ด๋ธ๋ทฐ์ ์ ์ ์ญ์ ํ๋ฉด์ ํ ์ด๋ธ๋ทฐ์ ๋ณด์ฌ์ค ๋ฐ์ดํฐ๋ ๋์ผํ๊ฒ ์ญ์ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ด์ผ ํ๋๋ฐ, ๋๋ฝ๋์ ๋ฐ์ํ ๊ฒ์ด์๋ค.ํด๊ฒฐ
์ ์ ์ถ๊ฐ, ์ญ์ ํ ๋ ํ ์ด๋ธ๋ทฐ์ ๋ณด์ฌ์ค ์น์ ์ ๊ฐ์๋ ๋์ผํ ์ ์๋๋ก PersistentManager์ notes ๊ด๋ฆฌ(๋ฐฐ์ด ์์ ์ ๊ฑฐ, ์ฝ์ด๋ฐ์ดํฐ ์์ ์ ๊ฑฐ)๋ ๋นผ๋จน์ง ์๋๋ก ํด์ฃผ์๋ค.
tableView์ Delegate ๋ฉ์๋๋ฅผ ํ์ฉํ ์ค์์ดํ ๊ธฐ๋ฅ ํ์ฉ
tableView์ Delegate ๋ฉ์๋๋ฅผ ํ์ฉํ์ฌ ์ ์ ์ค์์ดํ ํ์ ๋ ์ ํํ ์ ์๋ ์ต์ ์ ์ค์ ํ ์ ์๋ค.
// ์ค๋ฅธ์ชฝ์์ ์ผ์ชฝ์ผ๋ก ์ค์์ดํ ํ์ ๋์ ์ต์
์ค์
override func tableView(
_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration? {
// ...
}
// ์ผ์ชฝ์์ ์ค๋ฅธ์ชฝ์ผ๋ก ์ค์์ดํ ํ์ ๋์ ์ต์
์ค์
override func tableView(_ tableView: UITableView,
leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration? {
// ...
}
Core Data - Codegen
https://developer.apple.com/documentation/coredata/modeling_data/generating_code
ํ๋ก์ ํธ ํ๋ ๋์ค์ codegen์ ์ด๋ค ์ต์ ์ผ๋ก ์ค์ผํ ์ง ๊ฐ์ด ์์์ ์ฐพ์๋ณด์๋ค.
- ํด๋น entity์ ๋ํ ํด๋์ค ์ ์ธ์ ์๋์ผ๋ก ๋ง๋ค์ด ์ฃผ๋ ์ต์
์ ์ค์ ํฉ๋๋ค.
- None/Manual: ๊ด๋ จ ํ์ผ์ ์๋์ผ๋ก ๋ง๋ค์ด์ฃผ์ง ์๋๋ค. ๊ฐ๋ฐ์๋ DataModel์ ์ ํํ ์ํ์์ Editor-Create NSManagedObject Subclass ํญ๋ชฉ์ ํด๋ฆญํ์ฌ ํด๋์ค ์ ์ธ ํ์ผ๊ณผ ํ๋กํผํฐ extension ํ์ผ์ ๋น๋์๋ง๋ค ์ถ๊ฐ์์ผ ์ฃผ๊ณ , ์ด๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํด์ผ ํ๋ค.
- Class Definition: ํด๋์ค ์ ์ธ ํ์ผ๊ณผ ํ๋กํผํฐ ๊ด๋ จ extension ํ์ผ์ ๋น๋์๋ง๋ค ์๋์ผ๋ก ์ถ๊ฐ์์ผ์ค๋ค. ๋ฐ๋ผ์ ๊ด๋ จ๋ ํ์ผ์ ์ ํ ์ถ๊ฐ์์ผ์ค ํ์๊ฐ ์๋ค.(๊ทธ๋์๋ ์๋๋ค. ๋ง์ฝ ์๋์ผ๋ก ์ถ๊ฐ์์ผ์ค ์ํ์์ ๋น๋๋ฅผ ์๋ํ๋ฉด ์ปดํ์ผ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.)
- Category/Extension: ํ๋กํผํฐ ๊ด๋ จ extensionํ์ผ๋ง ์๋์ผ๋ก ์ถ๊ฐ์์ผ ์ค๋ค. ์ฆ, ํด๋์ค ์ ์ธ์๋ ์ฌ์ฉ์๊ฐ ์ํ๋ ๋ก์ง์ ์์ ๋กญ๊ฒ ์ถ๊ฐํ ์ ์๋ค.
NSFetchRequest - returnsObjectsAsFaults
https://developer.apple.com/documentation/coredata/nsfetchrequest/1506756-returnsobjectsasfaults
- CoreData์ ๊ด๋ฆฌํ๋ ๋ชจ๋ธ์ ์ค๊ณํ๋ค๊ฐ ์ด๋ฐ ํ๋กํผํฐ๋ฅผ ๋ฐ๊ฒฌํ๊ฒ ๋์๋ค.
- ๊ธฐ๋ณธ๊ฐ์ true์ธ ์์ฑ์ด๋ค.
- true์ธ ๊ฒฝ์ฐ Request๋ก ๊ฐ์ ธ์จ ๊ฐ์ฒด๊ฐ Faulting์ธ ๊ฒฝ์ฐ๋ผ๊ณ ํ๋ค.
- ์ฌ์ง๊ณผ ๊ฐ์ด Department๋ ํด๋์ค ์ธ์คํด์ค์ด๋ค. ๊ทธ๋ฆฌ๊ณ ์ธ์คํด์ค๊ฐ ์์ฑ๋์ด์์ง๋ง, ์์ฑ์ ๋น์ด์๋ ์ํ์ด๋ค. ์ด ์ํ๋ฅผ
๊ฒฐํจ์ด ์๋ค
๋ผ๊ณ ๋ณธ๋ค๋ ๊ฒ์ด๋ค. (์ด๋ฅผ ์ค๋ฅ๋ผ๊ณ ํจ) ๋ถ์๊ฐ ์กด์ฌํ์ง ์์ผ๋ ์ง์ ์ธ์คํด์ค๋ ์์ฑํ ํ์๊ฐ ์์ ๋ฟ๋ง ์๋๋ผ ๊ด๊ณ๋ ์ฑ์ธํ์๋ ์์์ ์๋ฏธํ๋ค. - ๊ทธ๋ํ๊ฐ ์์ ํด์ผ ํ๋ ๊ฒฝ์ฐ ์ง์์ ํ๋กํผํฐ๋ฅผ ํธ์งํ๋ ค๋ฉด ๊ถ๊ทน์ ์ผ๋ก ์ ์ฒด ๊ธฐ์ ๊ตฌ์กฐ๋ฅผ ๋ํ๋ด๋ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ผํ๋ค.
๋ฐ๋ผ์ returnsObjectsAsFaults๊ฐ true์ธ ๊ฒฝ์ฐ ์์ ๊ฐ์ ๊ฒฐํจ์ ๊ฐ์ง๊ณ ์๋ ๊ฒฝ์ฐ์๋ ์ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ Department ์ธ์คํด์ค๋ฅผ ์์ฑํ์ง์๋๋ค๋ ์ด์ผ๊ธฐ์ธ ๊ฒ ๊ฐ๋ค. ์ฆ ๊ฒฐํจ์ ํ์ฉํ๊ฒ ๋ค๋ ์๋ฏธ์ธ๊ฑธ๊น? false์ธ ๊ฒฝ์ฐ์๋ ๊ฒฐํจ์ด ์๋ ๋ง๋ ๋ชจ๋ ์ธ์คํด์ค๋ฅผ ๋ฐํํ๋๋ก ๊ฐ์ ํ๋ค๋ ๋ป์ธ ๊ฒ ๊ฐ๋ค.
- ๋ญ ๋ง์ธ์ง ์ ์ดํด๊ฐ ๊ฐ์ง ์์์ ์ข๋ ๊ณต๋ถ๊ฐ ํ์ํ ๊ฒ ๊ฐ๋ค...
- ์ค์ํ ๊ฒ์ returnsObjectsAsFaults ์ด ํ๋๊ทธ๊ฐ CoreData์ ๋งค์ฐ ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ธ Lazy loading๋ฅผ ์ํํ๋๋ก ์ง์ํ๋ค๊ณ ํ๋ค. [?]
๋ฉ๋ชจ๋ฆฌ ์ต์ ํ
๋ ์ฐ๊ด์ด ์๋ค๊ณ ...- https://ali-akhtar.medium.com/mastering-in-coredata-part-10-nsfetchrequest-a011684dd8f7
NSFetchRequestResult
https://developer.apple.com/documentation/coredata/nsfetchrequestresult
- FetchRequest๋ฅผ ๋ณด๋ผ๋ ๋จ์ํ NSManagedObject๋ง๊ณ ๋ค๋ฅธ ํ์ ๋ค๋ ์ ์ฐํ๊ฒ ๋ฐ๊ณ ์ถ๋ค๋ฉด, ์ด ํ๋กํ ์ฝ์ ํ์ฉํ ์ ์๋ค.
- Conforming Types
- NSDictionary
- NSManagedObject
- NSManagedObjectID
- NSNumber
UIContextualAction์ ํ ์คํธ๋ง๊ณ ์์ด์ฝ ์ฝ์ ํ๋ ๋ฐฉ๋ฒ
let deleteAction = UIContextualAction(style: .destructive, title: nil) { _, _, completeHandeler in
self.deleteCell(indexPath: indexPath)
completeHandeler(true)
}
deleteAction.image = UIImage(systemName: "trash.fill")
- ๋จผ์ UIContextualAction ์ธ์คํด์ค๋ฅผ ์์ฑํ๋ค.
- ์์ฑํ ๋ title์ด nil์ธ๊ฒ ํฌ์ธํธ์ด๋ค.
- ์ดํ ์์ฑํ UIContextualAction์ image๋ฅผ ๋์ ํด์ฃผ๋ฉด ๋๋ค.
์ ์ฉ๋ ๋ชจ์ต
์๋ฌธ์
UIContextualAction ํ๋ผ๋ฏธํฐ ์ค handler์ ์ฉ๋๋ ๋ฌด์์ผ๊น? https://developer.apple.com/documentation/uikit/uicontextualaction/handler
- handler์ ์์ ์ด ์ค์ ๋ก ์ํ๋ ๊ฒฝ์ฐ ํธ๋ค๋ฌ์ true๋ฅผ ์ ๋ฌํ์ฌ ์์ ์ด ์๋ฃ๋์๋ค๋ ๊ฒ์ ์๋ ค์ฃผ๋ ์ฉ๋๋ผ๊ณ ํ๋ค.
- ์ง๊ธ๊ฐ์ด ๊ฐ๋จํ ๋ก์ง์ธ ๊ฒฝ์ฐ ๊ทธ๋ฅ true๋ก ๊ธฐ์ ํด์ฃผ๋ฉด ๋๊ฒ ์ง๋ง, ๋ง์ฝ ๋ณต์กํ ๋ก์ง์ด ์ถ๊ฐ๋์ด ์๋ฌ์ฒ๋ฆฌ๋ฅผ ํด์ค์ผํ๋ ๊ฒฝ์ฐ์๋ false๋ฅผ ์ ๋ฌํ์ฌ ์์ ์ด ์๋ฃ๋์ง ์์๋ค๋ ๊ฒ์ ์๋ฆฌ๋ ์ฉ๋์ธ ๊ฒ ๊ฐ๋ค.
- https://developer.apple.com/forums/thread/129420
- ์ฌ๊ธฐ ๊ธ์ ์ฐธ๊ณ ํ๋ ํ์ฌ๋ completeHandeler๋ฅผ ์ฌ์ฉํ์ง ์์ง๋ง, ๋์ค์ ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก ์ ์ ํ ๊ฐ์ ์ ๋ฌํ๋ ๊ฒ์ ๊ถ์ฅํ๋ค๋ ๋ต๋ณ์ด ์๋ค.
- ๊ทธ๋์ ํ์๋ค๊ณผ ์๋ ผํ์ฌ true๋ก ๊ธฐ์ ํด์ฃผ๊ธฐ๋ก ํ์๋ค.
UIAlertAction ํธ์งํ๊ธฐ(titleTextAlignment, image)
๋จ์ํ ๊ธ์๋ง๊ณ ์ฌ๊ธฐ์๋ ์์ด์ฝ๊ฐ์๊ฑธ ์ถ๊ฐํ ์ ์์๊น?
let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: deleteHandler)
let deleteImage = UIImage(systemName: "trash.fill")
deleteAction.setValue(deleteImage, forKey: "image")
deleteAction.setValue(0, forKey: "titleTextAlignment")
- setValue ๋ฉ์๋๋ฅผ ํตํด์ ์ง์ ํด์ค ์ ์์๋ค.
- ๋จ์ํ๊ฒ ์ด๋ฏธ์ง์ ํ
์คํธ์ alignment๋ฅผ ์ง์ ํด์ฃผ์๋ค.
- 0 - left
- 1 - center
- 2 - right
์ ์ฉ๋ ๋ชจ์ต
- flatMap์ ํ์ฉํ์ฌ ์ต์ ๋ ๋ฐ์ธ๋ฉ ๋ถ๋ถ์ ๊ฐ๊ฒฐํ๊ฒ ๊ฐ์
- primary, secondary ์๋ก์ ์ด๋ฒคํธ ์ ๋ฌ์ SplitViewController๊ฐ ์๋๋ผ Delegate ํจํด์ผ๋ก ๊ฐ์
- ๊ฐ๋ ์ฑ์ด ๋จ์ด์ง๋ ๋ฉ์๋๋ฅผ ๋ฉ์๋๋ก ๋ถ๋ฆฌํ์ฌ ๊ฐ์
- ์ ์ ์ ํํ๊ณ ์ค์์ดํ ํ์ ๋ ์ ํ์ด ํด์ ๋๋ ๋ฒ๊ทธ ๊ฐ์
- ๋๋ณด๊ธฐ ๋ฒํผ์์ ๋ฉ๋ชจ๋ฅผ ์ญ์ ํ ํ ๋ค์ ๋ฉ๋ชจ์ฅ์ ๋ณด์ฌ์ฃผ๋๋ก ๊ฐ์
SwiftyDropbox ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ์ฌ ๋ฉ๋ชจ์ฅ๊ณผ ํด๋ผ์ฐ๋ ์ฐ๋์ ํฉ๋๋ค.
- ์ ๋ก๋ ํ๋ ์์ ์ ์ ํ ๋ ๊ณ ๋ คํ ๊ฒ์ ๋๋ฌด ๋น๋ฒํ๊ฒ ์ ๋ก๋๋ฅผ ํ์ง ์์์ผ ํ๋ฉฐ, ์ฑ์ด ์๋์น ์๊ฒ ์ข ๋ฃ๋์ด๋ ์ด๋์ ๋ ๋ฐฉ์ด๊ฐ ๋์ด์ผ ํ๋ค๋ ์ ์ด์๋ค.
- ์ฒ์์๋ ํ ์คํธ๋ทฐ์ ๋ณํ๊ฐ ์ผ์ด๋ ๋๋ง๋ค ํ๋ ค๊ณ ํ์์ผ๋ ๋๋ฌด ๋น๋ฒํ๊ฒ ์ผ์ด๋ ๊ฒ์ผ๋ก ๋ณด์๋ค. ๊ทธ๋์ ์ข ๋ฃํ ๋ ํ๋ ค๊ณ ํ๋ ์๋์น ์์ ์ข ๋ฃ์ ์ ํ ๋ฐฉ์ด๊ฐ ๋์ง ์์๋ค.
- ์ฌ๋ฌ ๊ณ ๋ฏผ์ ํ ๊ฒฐ๊ณผ ํค๋ณด๋๋ฅผ ๋ด๋ฆด ๋ ๋ง๋ค ์ ๋ก๋๋ฅผ ํ๋๋ก ํ์ฌ ์ ๋ก๋๊ฐ ๋น๋ฒํ๊ฒ ์ผ์ด๋์ง ์๋๋ก ํ์๊ณ ์๋์น ์๊ฒ ์ข ๋ฃ๋์ด๋ ๋ฐฉ์ด๋ฅผ ํ ์ ์๋๋ก ํ์๋ค.
- ๋ค์ด๋ก๋ ์์ ์ ์ฒ์์ ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ ์์ ์ dropbox์ ๋ฐ์ดํฐ๋ฅผ ๋ค์ด๋ก๋ ํ์ฌ ๋ณด์ฌ์ฃผ๋ ์ฃผ๋๋ก ๊ตฌํํ๊ธธ ์ํ๋ค. ๊ทธ๋์ ์ธ์ฆ์ด ์๋ฃ ๋๊ณ authResult(์ธ์ฆ๊ฒฐ๊ณผ)๊ฐ success๊ฐ ๋๋ฉด download๋ฅผ ํ๋๋ก ํ์๋ค.
- ๋ํ, ๋ค์ด๋ก๋๋ ๋น๋๊ธฐ๋ก ์งํ์ด ๋๊ธฐ ๋๋ฌธ์ DispatchGroup์ ์ฌ์ฉํ์ฌ ๋ค์ด๋ก๋๊ฐ ์๋ฃ๋๋ฉด ์ฑ์ ๋ฐ์ดํฐ๋ฅผ ๋ฟ๋ ค์ฃผ๊ณ ํ ์ด๋ธ๋ทฐ๋ฅผ ์ ๋ฐ์ดํธ ํ๋๋ก ๊ตฌํํ์๋ค.
- ๋ค์ด๋ก๋๊ฐ ์งํ๋ ๋์ ๋ทฐ๋ ์๋ฌด๊ฒ๋ ๋ณด์ฌ์ฃผ์ง ์๊ฒ ๋๋ค.
- ๋ก๋ฉ์ค์ด๋ผ๋ ๊ฒ์ ์ฌ์ฉ์์๊ฒ ์๋ ค์ฃผ๊ธฐ ์ํด์ ActivityIndicator๋ฅผ ์ฌ์ฉํ์๋ค.
- ๋ค์ด๋ก๋๋ฅผ ์์ฒญํ๊ณ ActivityIndicator๋ฅผ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๋๋ก ํ๊ณ ๋ค์ด๋ก๋๊ฐ ๋ชจ๋ ์๋ฃ๋๋ฉด ActivityIndicator๋ ์ข ๋ฃ๋๋ฉฐ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๋๋ก ๊ตฌํํ์๋ค.
- ScopeRequest์ scopes๋ ๋ญ๊น?
- CoreData์ ๊ฒฝ๋ก๋ ์ด๋์ผ๊น?
- CoreData๋ฅผ ๋ฎ์ด์ฐ๊ธฐ๊ฐ ์๋๋ผ ์ํ๋ ๋ฐ์ดํฐ๋ง ์ ๋ฐ์ดํธ ํด์ค ์๋ ์์๊น?
wal
,shm์ด
๋ฌด์จ๋ป์ผ๊น?
๋ค์ด๋ก๋๊ฐ ๋๋ ํ CoreData๋ฅผ fetch๋ฅผ ํ๊ณ TableView๋ฅผ reload๋ฅผ ํด์ฃผ๊ณ ์ถ์์ผ๋ ์คํจํ์๋ค.
์ด์
ํ์ผ์ด ์ฌ๋ฌ๊ฐ๊ฐ ์กด์ฌํ์ฌ, ์ฌ๋ฌ๊ฐ์ ํ์ผ์ ๋ค์ด๋ก๋ ํ๊ธฐ ์ํด ๋ฐ๋ณต๋ฌธ์ ๋๋ฆฌ๊ณ ์์์ผ๋, fetch์ reload๋ฅผ for-in๋ฌธ ๋ด๋ถ์์ ํด์ฃผ๊ณ ์์ด์, ๋ทฐ๊ฐ ์ ๋ฐ์ดํธ ๋ ๋๊ฐ ์๊ณ , ์๋๊ธฐ๋ ํ๋ ํ์์ด ๋ํ๋ฌ๋ค.
* `ํด๊ฒฐ` ๊ทธ๋์ `for-in๋ฌธ์ด ์ข ๋ฃ๋ ์์ `์ `fetch`๋ฅผ ํ๊ณ view๋ฅผ reload๋ฅผ ํด์ฃผ๊ธฐ ์ํด, ๋ค์ด๋ก๋๊ฐ ๋ชจ๋ ์๋ฃ๋๋ ์์ ์ `DispatchGroup`๋ฅผ ํ์ฉํ์ฌ `์ถ์ `ํ๊ณ , ๋ฐ๋ณต๋ฌธ์์ ์์๋์๋ ๋ค์ด๋ก๋ ์์ ์ด ๋ชจ๋ ๋๋๊ฒ ๋๋ฉด ์๋ ๋ทฐ๋ฅผ ๋ค์ ์ค์ ํ๋๋ก ์ฝ๋๋ฅผ ์์ ํ์๋ค.
func download(_ tableViewController: NotesViewController?) {
let group = DispatchGroup() // ๊ทธ๋ฃน ์์ฑ
for fileName in fileNames {
let destURL = applicationSupportDirectoryURL.appendingPathComponent(fileName)
let destination: (URL, HTTPURLResponse) -> URL = { _, _ in
return destURL
}
group.enter() // ์์
์์
client?.files.download(path: fileName, overwrite: true, destination: destination)
.response { _, error in
if let error = error {
print(error)
}
group.leave() // ์์
๋
}
}
group.notify(queue: .main) { // ๋ชจ๋ ์์
์ด ๋๋๋ค๋ฉด ...
PersistentManager.shared.setUpNotes()
tableViewController?.tableView.reloadData()
tableViewController?.stopActivityIndicator()
}
}
[Swift Package Manager๋ฅผ ์ฌ์ฉํ์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ฉํ๊ธฐ]
Targets
->General
->Frameworks, Libraries, and Embedded Content
->+
Add Package Dependency...
๋ฅผ ํด๋ฆญ
์ฌ์ฉํ๊ณ ์ถ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ฃผ์๋ฅผ ๊ธฐ์ ํ๋ค.
์ค์น ์ ์ํ๋ ๋ฒ์ , ๋ธ๋์น ๋ฐ ์ปค๋ฐ์ ์ค์ ํ ์ ์๋ค. ์ด ํ ์ํ๋ packge product๋ฅผ ๊ณจ๋ผ์ Finish ๊น์ง ํ๋ฉด...
SwiftyDropbox
๊ฐ ์ ์์ ์ผ๋ก ์ค์น๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
[ํ๋ก์ ํธ์ SwiftyDropbox ์ค์ ํ๊ธฐ]
์๋ ํ๋ก์ ํธ ์ค์ ํ๋ ํํ ๋ฆฌ์ผ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์๋ค.
https://github.com/dropbox/SwiftyDropbox#configure-your-project
๋จผ์ Info.plist ํ์ผ์ ์์ ํด์ฃผ์ด์ผ ํ๋๋ฐ, ๊ทธ ์ ์ dropbox์ app์ ๋ฑ๋กํด์ผ ํ๋ค. ๋ก๊ทธ์ธ ํ apps์ ๋ค์ด๊ฐ๋ฉด ์๋์ ๊ฐ์ ๋ฒํผ์ด ์๋ค.
์ดํ ํ์ ๋ฌธํญ์ ์ ํ, ์ ๋ ฅ ํ create app ๋ฒํผ์ ๋๋ฌ ๋ง๋ค์ด์ฃผ๋ฉด ๋๋ค.
๊ทธ๋ฌ๋ฉด App key๊ฐ ๋ฐ๊ธ๋๋๋ฐ, ์ด๊ฑธ ์ด์ Info.plist๋ฅผ ์์ ํ๋๋ฐ ํ์ฉํ ๊ฒ์ด๋ค.
ํํ ๋ฆฌ์ผ์์ ํ๋ผ๋๋ฐ๋ก Info.plist๋ฅผ ์์์ ๊ฐ์ด ์์ ํด์ค๋ค.
<key>LSApplicationQueriesSchemes</key>
<array>
<string>dbapi-8-emm</string>
<string>dbapi-2</string>
</array>
์๊น ๋ง๋ค๊ณ ์ป์ App key๋ฅผ
db-
๋ค๋ถํฐ ๊ธฐ์ ํด์ฃผ๋ฉด ๋๋ค.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>db-<APP_KEY></string>
</array>
<key>CFBundleURLName</key>
<string></string>
</dict>
</array>
์ดํ ์ฝ๋๋ก ๋์๊ฐ์ AppDelegate์ DropboxClient ์ธ์คํด์ค๋ฅผ ์ด๊ธฐํ ํด์ค๋ค.
import SwiftyDropbox
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
DropboxClientsManager.setupWithAppKey("<APP_KEY>")
return true
}
๊ทธ๋ฆฌ๊ณ SceneDelegate์ ์๋์ ๊ฐ์ ๋ฉ์๋๋ฅผ ์ถ๊ฐํ๋ค.
import SwiftyDropbox
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
let oauthCompletion: DropboxOAuthCompletion = {
if let authResult = $0 {
switch authResult {
case .success:
print("Success! User is logged into DropboxClientsManager.")
case .cancel:
print("Authorization flow was manually canceled by user!")
case .error(_, let description):
print("Error: \(String(describing: description))")
}
}
}
for context in URLContexts {
// stop iterating after the first handle-able url
if DropboxClientsManager.handleRedirectURL(context.url, completion: oauthCompletion) { break }
}
}
}
์ดํ ๋งจ์ฒ์์ ์์ํ๋ ๋ทฐ์ ๋ก๊ทธ์ธ์ ํด์ ์ธ์ฆ ํ ํฐ์ ๋ฐ์์ค๋ ์์ ์ ์ถ๊ฐํ๋ค. ์ด๋ฒ ํ๋ก์ ํธ ๊ฐ์ ๊ฒฝ์ฐ UISplitViewController๋ฅผ ์ฌ์ฉํ๋๋ฐ, rootView์ธ SplitViewController์์๋ ํด๋น ์์ ์ด ์ ์์ ์ผ๋ก ๋จ์ง์์๋ค. (์ด์ ๋ ์ฐพ์ง ๋ชปํ๋ค.) ๊ทธ๋์ ๋ค๋ฅธ UIViewController์์ ์งํํด์ผํ๋.. ์ถ์ด์ UITableViewController์ viewDidLoad()์์ ํด๋น ์์ ์ ์คํํด์ฃผ๋ ๋ก๊ทธ์ธ์ฐฝ์ด ์ ์์ ์ผ๋ก ๋ด๋ค.
import SwiftyDropbox
func myButtonInControllerPressed() {
// OAuth 2 code flow with PKCE that grants a short-lived token with scopes, and performs refreshes of the token automatically.
let scopeRequest = ScopeRequest(scopeType: .user, scopes: ["account_info.read"], includeGrantedScopes: false)
DropboxClientsManager.authorizeFromControllerV2(
UIApplication.shared,
controller: self,
loadingStatusDelegate: nil,
openURL: { (url: URL) -> Void in UIApplication.shared.open(url, options: [:], completionHandler: nil) },
scopeRequest: scopeRequest
)
// Note: this is the DEPRECATED authorization flow that grants a long-lived token.
// If you are still using this, please update your app to use the `authorizeFromControllerV2` call instead.
// See https://dropbox.tech/developers/migrating-app-permissions-and-access-tokens
DropboxClientsManager.authorizeFromController(UIApplication.shared,
controller: self,
openURL: { (url: URL) -> Void in
UIApplication.shared.open(url, options: [:], completionHandler: nil)
})
}
์ฌ๊ธฐ์ scopes๋ผ๋ ํ๋ผ๋ฏธํฐ๊ฐ ์๋๋ฐ, ์ด ๋ถ๋ถ์ ์ฑ์ด Dropbox ๊ณ์ ์ ๋ณด๋ฅผ ๋ณด๊ณ ๊ด๋ฆฌํ ์ ์๋๋ก ๊ถํ์ ๋ฒ์๋ฅผ ๋ปํ๋ค. ์๊น App key๋ฅผ ์ป์๋ ๊ณณ์์ Permissions ํญ์ ํด๋ฆญํ๋ฉด Account์ ์ ๋ณด๊ฐ ๋์จ๋ค. ๋ฐ๋ผ์ ํ์ํ Account๋ฅผ scopes์ ๋ฃ์ด์ฃผ๋ฉด ๋๊ฒ ๋ค.
์ด ๋ค์์ API์ ํธ์ถํ DropboxClient ์ธ์คํด์ค๋ฅผ ์์ฑํ๋ค.
import SwiftyDropbox
// Reference after programmatic auth flow
let client = DropboxClientsManager.authorizedClient
client๋ฅผ ํตํด ์ ๋ก๋์ ๋ค์ด๋ก๋๋ฅผ ์งํํ ์ ์๋ค.
let fileData = "testing data example".data(using: String.Encoding.utf8, allowLossyConversion: false)!
let request = client.files.upload(path: "/test/path/in/Dropbox/account", input: fileData)
.response { response, error in
if let response = response {
print(response)
} else if let error = error {
print(error)
}
}
.progress { progressData in
print(progressData)
}
// in case you want to cancel the request
if someConditionIsSatisfied {
request.cancel()
}
// Download to URL
let fileManager = FileManager.default
let directoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destURL = directoryURL.appendingPathComponent("myTestFile")
let destination: (URL, HTTPURLResponse) -> URL = { temporaryURL, response in
return destURL
}
client.files.download(path: "/test/path/in/Dropbox/account", overwrite: true, destination: destination)
.response { response, error in
if let response = response {
print(response)
} else if let error = error {
print(error)
}
}
.progress { progressData in
print(progressData)
}
// Download to Data
client.files.download(path: "/test/path/in/Dropbox/account")
.response { response, error in
if let response = response {
let responseMetadata = response.0
print(responseMetadata)
let fileContents = response.1
print(fileContents)
} else if let error = error {
print(error)
}
}
.progress { progressData in
print(progressData)
}
๋๊ฐ์ง์ ๊ณตํต์ ์ ํ์ผ์ ๋ค์ด๋ก๋ํ๊ณ , ์ ๋ก๋ํ ๋
๊ฒฝ๋ก
๊ฐ ํ์ํ๋ค๋ ์ ์ด๋ค. ๋ฉ๋ชจ์ฅ ํ๋ก์ ํธ์ ๊ฒฝ์ฐ CoreData๋ฅผ ํตํด์ ๋ฉ๋ชจ๋ฅผ ๊ด๋ฆฌํ๊ณ ์๊ธฐ ๋๋ฌธ์๋ฐฑ์
์ ํํ๋ก CoreData์ ๊ฒฝ๋ก๋ฅผ ์ป์ด๋ด์.sqlite
,.sqlite-shm
,.sqlite-wal
์ด 3๊ฐ์ ํ์ผ์ ์ ๋ก๋ ๋ฐ ๋ค์ด๋ก๋ ํด์ฃผ๋๋ก ๊ตฌํํด์ฃผ์๋ค.
์ ๋ก๋, ๋ค์ด๋ก๋ ๋ชจ๋ ํ์ผ์ ๋ฎ์ด์ธ๊ฑด์ง์ ๋ํ ์ต์ ์ด ์์ผ๋ ์์ธํ๊ฑด ์๋ ๋ํ๋จผํธ์์ ๊ฒ์ํด๋ณด๋ฉด ๋๊ฒ ๋ค.
https://dropbox.github.io/SwiftyDropbox/api-docs/latest/index.html
- ๋ค์ด๋ก๋ ์ค์ผ ๋ ํฐ์น๋ฅผ ์ ํํ๋ ๊ฒ์ด ์๋๋ผ indicator๊ฐ ์ถ๊ฐ๋ ์ปจํธ๋กค๋ฌ๋ก ํ๋ฉด์ ๋ฎ๊ธฐ
- ๋ค์ด๋ก๋, ์ ๋ก๋์ ์๋ฌ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๋๋ก ๊ฐ์
- PersistentManager์ discardableResult ์ต์ ์ ์ ๊ฑฐ
- NotesViewController์ setEditing ๋ด๋ถ if๋ฌธ ๋ก์ง์ deleteCell ๋ฉ์๋๋ก ์ด๋
- ์ง์ญํ, ์ ๊ทผ์ฑ, ๊ฒ์๊ธฐ๋ฅ ๋ฑ์ ์ถ๊ฐ ๊ตฌํํฉ๋๋ค.
- ๊ฒ์์ด๋ฅผ ์
๋ ฅํ ๋๋ง๋ค ๊ฒ์์ด์ ํด๋นํ๋ ๋ฉ๋ชจ๋ฅผ
์ค์๊ฐ
์ผ๋ก ๋ณด์ฌ์ค ์ ์๋๋กNSPredicate
๋ฅผ ํ์ฉํ์ฌ fetchํ๋ ๊ธฐ๋ฅ์ ์ถ๊ฐ - ๊ฒ์์ด๊ฐ
๋น ๋ฌธ์์ด("")
์ด๋ผ๋ฉด ๋ค์ ๋ฉ๋ชจ์ ์ ์ฒด๋ชฉ๋ก์ ๋ณด์ฌ์ค ์ ์๋๋ก ๊ตฌํ
์ธ์ด
์์ด๋ฅผ ๊ธฐ๋ณธ์ค์ ์ผ๋ก ํ๊ณ ํ๊ตญ์ด์ ๋ํด์๋ ๋ค๊ตญ์ดํ๋ฅผ ์ง์ํ๋๋ก ๊ตฌํ๋ ์ง
์๊ฐ์ ์์คํ ์๊ฐ์ ๋ฐ๋ฅด๋๋ก ํ๊ณ , ๋ ์ง ํ์์ ์ธ์ด์ ๋ฐ๋ผ ํฌ๋งท์ ๋ณ๊ฒฝ๋๋๋ก ๊ตฌํ
- ์ฐ์ธก ๋ฉ๋ชจ ์์ธ ๋ด์ฉ์ ๊ฒฝ์ฐ VoiceOver๊ฐ ๋ฆฌ์คํธ๋ฅผ ์ฝ์ ํ, ํ
์คํธ๋ฅผ ๋ฐ๋ก ์ฝ์ด์ฃผ๋ ๊ฒ์ ํ์ธํ๊ณ ,
๋ฉ๋ชจ ๋ด์ฉ
์ด๋ผ๋accessibilityLabel
์ ์ถ๊ฐํ์ฌ, ๋ฉ๋ชจ ํ ์คํธ๋ฅผ ์ฝ๊ธฐ ์ ์ ์ข ๋ ํ๋ฉด์ ๊ตฌ์ฑ์์๋ฅผ ์ดํดํ๊ธฐ ์ฝ๋๋ก ๊ตฌํ - View์ ํ
์คํธ ์์๋ค์
Dynamic Type
์ ์ ์ฉํ์ฌ ์ฌ์ฉ์๊ฐ ์ํ๋ ์ฌ์ด์ฆ๋ก ํ ์คํธ ํฌ๊ธฐ๋ฅผ ์ค์ ํ ์ ์๋๋ก ์ ์ฐ์ฑ์ ์ ๊ณต
Cancel
์ด๋OK
๊ฐ์ ๊ฒ๋ค์ ์๋์ผ๋ก ์ง์ญํ๊ฐ ๋์ง ์๋๊ฑธ๊น?UITableView
์๋accessibilityLabel
์ ์ถ๊ฐํด์ค ์ ์๋๊ฑธ๊น?- CoreData๋ฅผ ๋ถ๋ฌ์ค๋
fetch
๊ธฐ๋ฅ์ ์ฌ๋ฌ๋ฒํ๋ฉด ๋น์ฉ์ด ๋ง์ด ๋๋๊ฑธ๊น...? - Dropbox์์ ๋ฐฑ์ ํ๋ ํ์ผ์ ๋ค์ด๋ก๋ํด์ CoreData๋ฅผ ๋ฎ์ด์ฐ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฌ๊ฐ ๋๋๋ฐ, ์ฑ์ ์ ์์ ์ผ๋ก ์๋ํ๋ค. ์ด ๋ถ๋ถ์ ์ ๊ฒฝ์ฐ์ง ์์๋ ๋ ๊น?
- Accessibility Inspector๋ฅผ ํ์ฉํ์ฌ ์ ๊ทผ์ฑ์ ์ํด Run Audit์ ํตํด ๊ฐ์ ํ ํญ๋ชฉ๋ค์ด ์๋์ง ๊ฒ์ํ์๋ค.
- ๊ทธ๋ฆฌ๊ณ VoiceOver๋ฅผ ์ง์ ์คํํด์ ํ ์คํธํด๋ณด๋ฉฐ ๋ถ์กฑํ ๋ถ๋ถ์ด ์๋์ง ํ์ธํด๋ณด์๋ค.
- ๋ค์ด๋๋ฏน ํ์ ์ ๊ฒฝ์ฐ๋ ํ ์คํธ ํฌ๊ธฐ๊ฐ ์ ์ฐํ์ง ๊ฒ์ํ์๋ค.
[Localization]
- ์ง์ญํ๋ ํ์งํํ๋ค๋ ๋ป์ ๊ฐ์ก๋ค
- ์ฆ, ํด๋น ์ธ์ด์ ๋๋ผ ์ง์ญ์ ๋ง๊ฒ ์ฑ์ ์ค์ ํด์ฃผ๋ ๊ฒ์ ๋ปํ๋ค.
- ๊ตญ์ ํ(internationalization)๋ฅผ I18Nย orย i18n์ผ๋ก, ์ง์ญํ(localization)๋ฅผย L10N์ด๋ย l10n์ผ๋ก ํ๊ธฐํ๋ค
- ํด๋น ์ฑ์ด ์ง์ญํ๊ฐ ๋๋ ค๋ฉด ์ฌ๋ฌ ๊ตญ๊ฐ์ ๋ฐฐํฌ๋์ด ๊ตญ์ ํ ๋์ด์๋ ์ฑ์ด๋ผ๋ ์กฐ๊ฑด์ด ์์ด์ผ ํ๋ค.
- ํด๋น ์ฑ์ด ํ๊ตญ์์๋ง ์ฌ์ฉ๋๋ ์ฑ์ด๋ผ๋ฉด ์ง์ญํ๊ฐ ์๋ฏธ ์์ ๊ฒ์ด๋ค.
- RTL, LTR (๋ฌธํ๊ถ์ ๋ฐ๋ฅธ ์ฝ๊ธฐ/์ฐ๊ธฐ ๋ฐฉ์), ์ธ์ด, ์๊ฐ, ๋ ์ง, ์ฃผ์, ํํ๋จ์ ๋ฐ ํตํ, ์ด๋ฏธ์ง ๋ฑ๋ฑ...
- ์ง์ญํ๋ฅผ ํจ์ผ๋ก ์ฌ๋ฌ ๊ตญ๊ฐ์ ์ง์ญ์์ ํด๋น ์ฑ์ ๋ํ ์ ๊ทผ์ฑ(accessibility)๊ฐ ์ฐ์ํด์ง๋ค.
- ์ ๊ทผ์ฑ์ ์ ํ์ ๊ฐ์ฅ ๊ฐ์ ์ธ ๋ถ๋ถ์ผ๋ก ๊ผญ ์ด ๋ถ๋ถ์ ์ ํ์ฉํ์ฌ ๊ตฌํํด๋์ผ๋ฉด ์ข๋ค.
- ์ ๊ทผ์ฑ(accessibility)์ ์ค์ ํ๋ ค๋ฉด accessibility inspector๋ฅผ ํ์ฉํ์ฌ ์ฌ๋ฌ๊ฐ์ง๋ฅผ ๊ตฌํํ ์ ์๋ค.
- ์ง์ญํ ํ๋ ค๋ ์ธ์ด๋ฅผ ํ๋ก์ ํธ์ ์ถ๊ฐํ๋ค.
- ํ๊ฒ์ ์ ํํด์ ๋ค๊ตญ์ดํ
- ์ฝ๋๋ก ๋ค๊ตญ์ด ์ฒ๋ฆฌ
- Strings ํ์ผ์ ์์ฑํ๊ณ
Localizable.strings
๋ก ๋ค์ด๋ฐ ๋ณ๊ฒฝ
- Strings ํ์ผ์ ์์ฑํ๊ณ
- Localize... ๋ฒํผ ํด๋ฆญ
- ๋ค์ ํ๊ฒ์ผ๋ก ๋์๊ฐ์ ์ง์ญํํ๊ณ ์ถ์ ์ธ์ด๋ฅผ ์ถ๊ฐํด์ฃผ๊ธฐ
- ์๊น ๋ง๋ ํ์ผ์ ์ฒดํฌํด์ฃผ๊ณ Finish
- ํ๋ก์ ํธ์ ํ์ผ์ด ์์ฑ๋์ด ์๋ ๋ชจ์ต
- Localizable.strings์ ๋ค๊ตญ์ด ์ฒ๋ฆฌ๋ฅผ ํ ์ฃผ๋ฉด ๋๋๋ฐ, Key์ Value๋ก ๋ค๊ตญ์ด ์ฒ๋ฆฌ๋ฅผ ํด์ค ์ ์๋ค.
- ๊ทธ๋ฆฌ๊ณ ๋ค๊ตญ์ดํ ํ ๋ฌธ์์ด์ ์ฌ์ฉํ ๋
NSLocalizedString
๋ฉ์๋๋ฅผ ํ์ฉํด์ฃผ์ด์ผ ํ๋๋ฐ, ๋ฒ๊ฑฐ๋ก์ฐ๋ extension์ ํ์ฉํ์ฌ ๊ฐ๋จํ ์ฌ์ฉํด๋ณผ ์ ์๋ค.
test.text = String(format: NSLocalizedString("Test", comment: ""))
// String Extension for Localization
extension String {
var localized: String {
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
}
}
text.text = "Test".localized
์คํ ๋ฆฌ๋ณด๋๋ฅผ ์ฝ๋๋ก ๋ง๊ณ Interface Builder Storyboard ์ต์ ์ ํ์ฉํ์ฌ ์คํ ๋ฆฌ๋ณด๋ ์์ฒด๋ฅผ ์ง์ญํํด์ค ์๋ ์๋ค.
์ฑ์ ์ธ์ด๋ฅผ ๋ฐ๊ฟ ๋๋ App Language, App Region ๋๋ค ๋ฐ๊ฟ์ฃผ์.
์ด๋ฏธ์ง์ ์ง์ญํ๋ Assets์ ์ ๊ทผํด์ ์ด๋ฏธ์ง๋ฅผ ํด๋ฆญํ ์ฐ์ธก ์ธ์คํํฐ์์ Localization์ ํ์ฑํ ์์ผ์ฃผ๋ฉด ๋๋ค.
๋ ์ง ์ง์ญํ
let date = DateFormatter.localizedString(from: Date(), dateStyle: .medium, timeStyle: .short)
dateTimeLabel.text = date
ํตํ ์ง์ญํ
func currency(text: Double) -> String? {
let locale = Locale.current
let price = text as NSNumber
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = locale.languageCode
formatter.locale = locale
return formatter.string(from: price)
}
currencyLabel.text = currency(text: 3000.34)
๋ทฐ์ ๋ฐฉํฅ์ ์ง์ญํ (๋ฐฉํฅ ๋ฐ๊ฟ ๋์๋ ์ ์ฉํ๊ฒ ์ฐ๋ ๋ฏ?)
view.semanticContentAttribute = .forceRightToLeft
์ฌ๋ฌ ๋ฌธ์์ด๋ค์ ์ง์ญํํ ๋ ๊ตฌ๊ธ ์คํ๋ ๋ ์ํธ๋ฅผ ํ์ฉํ๊ธฐ
- ๊ตฌ๊ธ ์คํ๋ ๋ ์ํธ๋ฅผ ์๋ก ์์ฑํ ํ ์ ์ฌ์ง๊ณผ ๊ฐ์ด ๊ตญ๊ฐ์ฝ๋์ ๋ฒ์ญํ ๋ฌธ์ฅ์ ์ ์ผ๋ฉด ๋๋ค.
- ์ข์ธก์ ๊ตญ์ ์ฝ๋๋ฅผ ์ ๊ณ ์ฐ์ธก์ ์๋ ์ฝ๋๋ฅผ ์ ์ผ๋ฉด ๋ฒ์ญํ ๋ฌธ์ฅ์ด ์์ฑ๋๋ค. (๊ตญ๊ฐ์ฝ๋ ์ฐธ๊ณ ์ฌ์ดํธ)
// ์์
=GOOGLETRANSLATE("Welcome to yagom-academy", "en", A10)
- ๊ตญ๊ฐ๋ณ๋ก ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ด ๋ค๋ฅด๊ฒ ํด์ผํ๋ ์ ๋ ์ฐธ๊ณ ํ์.
- ๊ตญ๊ฐ๋ณ ๊ธฐ๋ฅ์ฐจ์ด๋ฅผ ๋๋ ์ด์ ๋ ํน์ ํ๋ฉด์ด๋ ๊ธฐ๋ฅ์ด ํน์ ๋๋ผ์์๋ ์ฌ์ฉํ๋ฉด ์๋๋ ๊ฒ์ด๋ผ๋์ง, ํน์ ๋๋ผ์ ํจ๊ณผ์ ์ผ๋ก ๋ฐ์นญํ๊ธฐ ์ํด ์๋ก์ด ๊ธฐ๋ฅ์ ๋์ ํ๋ค๋์ง, ๋น์ฆ๋์ค ์ ์ธ ์ด์ ๊ฐ ๋ค์ํ๋ค.