This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
| swift [2022/12/26 19:09] admin | swift [2023/11/19 22:16] (current) admin | ||
|---|---|---|---|
| Line 7: | Line 7: | ||
| `struct ScrumsView_Previews: PreviewProvider` is just for showing an mock up preview when you coding. It is not a part of the real app. | `struct ScrumsView_Previews: PreviewProvider` is just for showing an mock up preview when you coding. It is not a part of the real app. | ||
| - | {{ :swiftui_binding_state.png?400|}} | ||
| Add `@State` at the beginning to declear a single source of truth at the very most parent view. Use `$[variable name]` to pass it to child view. In child view initialize with `@Binding var [variable name]: [variable type]` to use that data. | Add `@State` at the beginning to declear a single source of truth at the very most parent view. Use `$[variable name]` to pass it to child view. In child view initialize with `@Binding var [variable name]: [variable type]` to use that data. | ||
| Line 20: | Line 19: | ||
| Task modifier for async loading contents in the background. [ref](https://www.hackingwithswift.com/quick-start/swiftui/how-to-run-an-asynchronous-task-when-a-view-is-shown). Use continuations to bridge between **asynchronous functions** and existing **callback-based function**s. | Task modifier for async loading contents in the background. [ref](https://www.hackingwithswift.com/quick-start/swiftui/how-to-run-an-asynchronous-task-when-a-view-is-shown). Use continuations to bridge between **asynchronous functions** and existing **callback-based function**s. | ||
| + | |||
| + | ## Class | ||
| + | |||
| + | ### @Published wrapper | ||
| + | In this code, Why there needs to be a @Published wrapper? | ||
| + | |||
| + | ``` | ||
| + | class MyStore: ObservableObject { | ||
| + | @Published var selection:Int? | ||
| + | |||
| + | func sendID(_ id: Int) { | ||
| + | self.selection = id | ||
| + | } | ||
| + | } | ||
| + | ``` | ||
| + | |||
| + | The @Published wrapper is used in SwiftUI to automatically notify any views that are observing the property when its value changes. | ||
| + | |||
| + | In the given code, the selection property is marked as @Published, indicating that it is an observable property. When the sendID(_:) function is called and the value of selection is updated with a new ID, the @Published wrapper will automatically send notifications to any views that are observing this property. These views will then be updated accordingly to reflect the new value of selection. | ||
| + | |||
| + | By using the @Published wrapper, you eliminate the need to manually send notifications and manage observers, making it easier to update and synchronize data between different parts of your SwiftUI app. | ||
| + | |||
| + | |||
| + | ``` | ||
| + | struct ClosureDemo: View { | ||
| + | @StateObject var store = MyStore() | ||
| + | var body: some View { | ||
| + | if let currentID = store.selection { // currentID won't be updated if there is no @Publish wrapper in MyStore class | ||
| + | Text("Current ID: \(currentID)") | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | class MyStore: ObservableObject { | ||
| + | @Published var selection:Int? | ||
| + | |||
| + | func sendID(_ id: Int) { | ||
| + | self.selection = id | ||
| + | } | ||
| + | } | ||
| + | ``` | ||
| + | |||
| + | ## Data Storage | ||
| + | |||
| + | ### UserDefaults | ||
| + | is used to store small user preferences and application settings. It has been available as part of the Foundation framework in iOS and macOS long before SwiftUI was introduced. | ||
| + | |||
| + | ### AppStorage | ||
| + | is just a wrapper of UserDefaults in Swift UI. | ||
| + | |||
| + | ### iCloud Key-Value store (NSUbiquitousKeyValueStore) | ||
| + | You can think of it like UserDefaults, but the value is shared across different devices for the same app, that ties to an Apple ID account. | ||
| + | |||
| + | |||
| + | ref: | ||
| + | [[https://developer.apple.com/library/archive/documentation/General/Conceptual/iCloudDesignGuide/Chapters/Introduction.html#//apple_ref/doc/uid/TP40012094|Apple: iCloud Design Guide]] | ||
| + | [[https://developer.apple.com/library/archive/documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesigningForKey-ValueDataIniCloud.html#//apple_ref/doc/uid/TP40012094-CH7|Apple: Designing for Key-Value Data in iCloud]] | ||
| + | |||
| + | ### Relationship: Connect entities in CoreData | ||
| + | ref: https://www.hackingwithswift.com/books/ios-swiftui/one-to-many-relationships-with-core-data-swiftui-and-fetchrequest | ||
| + | |||
| + | 1. Create Entities in CoreData | ||
| + | 2. Select the relationship created, in the right tool panel, change `Type` to `To Many` | ||
| + | 3. Select the entity, in the right tool panel, change `Codegen` to `Manual/None` | ||
| + | 4. Use `Editor>Create NSManagedObject SubClass...` menu to create `...CoreDataClass` file and `...CoreDataProperties` file for each entity. | ||
| + | 5. In `...CoreDataProperties` file, you should be able to see code like below for adding transactions to this certain piggy object. | ||
| + | |||
| + | ``` | ||
| + | extension Piggy { | ||
| + | |||
| + | @nonobjc public class func fetchRequest() -> NSFetchRequest<Piggy> { | ||
| + | return NSFetchRequest<Piggy>(entityName: "Piggy") | ||
| + | } | ||
| + | |||
| + | @NSManaged public var capacity: NSDecimalNumber? | ||
| + | @NSManaged public var currentMoney: NSDecimalNumber? | ||
| + | @NSManaged public var dailyAllowance: NSDecimalNumber? | ||
| + | @NSManaged public var id: UUID? | ||
| + | @NSManaged public var lastDepositDay: Date? | ||
| + | @NSManaged public var name: String? | ||
| + | @NSManaged public var totalSaved: NSDecimalNumber? | ||
| + | @NSManaged public var transactions: NSSet? | ||
| + | } | ||
| + | extension Piggy { | ||
| + | @objc(addTransactionsObject:) | ||
| + | @NSManaged public func addToTransactions(_ value: PurchasedItem) | ||
| + | |||
| + | @objc(removeTransactionsObject:) | ||
| + | @NSManaged public func removeFromTransactions(_ value: PurchasedItem) | ||
| + | |||
| + | @objc(addTransactions:) | ||
| + | @NSManaged public func addToTransactions(_ values: NSSet) | ||
| + | |||
| + | @objc(removeTransactions:) | ||
| + | @NSManaged public func removeFromTransactions(_ values: NSSet) | ||
| + | } | ||
| + | ``` | ||
| + | |||
| + | 1. Edit ` ...CoreDataProperties` file, add code like `public var wrappedCapacity: NSDecimalNumber { capacity ?? 0.0 }` | ||
| + | 2. In ` ...CoreDataProperties` file, `transactions` is an `NSSet`, perform the following steps to convert it from an `NSSet` to a `Set<PurchasedItem>` – a Swift-native type where the types of its contents is specificy to `PurchasedItem` type. Convert that `Set<PurchasedItem>` into an array, so that `ForEach` can read individual values from there. Sort that array, so the transactions come in a sensible order. | ||
| + | |||
| + | ``` | ||
| + | public var transactionArray: [PurchasedItem] { | ||
| + | let set = transactions as? Set<PurchasedItem> ?? [] | ||
| + | |||
| + | return set.sorted { | ||
| + | $0.wrappedTimestamp < $1.wrappedTimestamp | ||
| + | } | ||
| + | } | ||
| + | ``` | ||
| + | |||
| + | 1. Use them in your View: To read purchased items connected to a certain piggy in a View. Use the following code: | ||
| + | |||
| + | ``` | ||
| + | struct HistoryView: View { | ||
| + | @Environment(\.managedObjectContext) private var viewContext | ||
| + | @EnvironmentObject private var persistenceController: PersistenceController | ||
| + | let piggy: Piggy | ||
| + | |||
| + | @FetchRequest private var items: FetchedResults<PurchasedItem> | ||
| + | |||
| + | init(piggy: Piggy) { | ||
| + | _items = FetchRequest<PurchasedItem>( | ||
| + | sortDescriptors: [NSSortDescriptor(keyPath: \PurchasedItem.timestamp, ascending: false)], | ||
| + | predicate: NSPredicate(format: "piggyStored == %@", piggy) | ||
| + | ) | ||
| + | self.piggy = piggy | ||
| + | } | ||
| + | |||
| + | var body: some View { | ||
| + | NavigationView { | ||
| + | if (items.count != 0) { | ||
| + | List { | ||
| + | ForEach(items) { item in | ||
| + | // view related code | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | ``` | ||
| + | |||
| + | Or use the following code. However with this method the HistoryView won't update if you add new purchasedItem (transaction) because it is only monitoring the object `piggy`. To trigger view update when purchasedItem is added, you need to use the method above to fetch `PurchasedItem` directly. | ||
| + | |||
| + | ``` | ||
| + | struct HistoryView: View { | ||
| + | @Environment(\.managedObjectContext) private var viewContext | ||
| + | @EnvironmentObject private var persistenceController: PersistenceController | ||
| + | let piggy: Piggy | ||
| + | |||
| + | var body: some View { | ||
| + | NavigationView { | ||
| + | if (piggy.transactionArrary.count != 0) { | ||
| + | List { | ||
| + | ForEach(piggy.transactionArray) { item in | ||
| + | // view related code | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | ``` | ||
| + | |||
| + | 1. To write new purchased item and connect it to piggy. | ||
| + | |||
| + | ```swiftUI | ||
| + | let newPurchase = PurchasedItem(context: viewContext) | ||
| + | newPurchase.id = UUID() | ||
| + | newPurchase.type = 0 // 0: purchase | ||
| + | newPurchase.name = name | ||
| + | newPurchase.price = NSDecimalNumber(decimal: Decimal(price)) | ||
| + | newPurchase.timestamp = Date() | ||
| + | newPurchase.piggyStored = piggy | ||
| + | try? viewContext.save() | ||
| + | ``` | ||
| + | |||
| + | |||
| + | |||
| + | ## Icons | ||
| + | |||
| + | How to import and use custom icons: | ||
| + | |||
| + | 1. Download icons svg that is made in proper format: https://fonts.google.com/icons?icon.style=Rounded&icon.set=Material+Symbols&icon.platform=ios | ||
| + | 2. Import the svg file into Assets in Xcode | ||
| + | 3. Use it in the code, for example: `barItem: UITabBarItem(title: "Piggy", image: UIImage(named: "piggy.saving"))` | ||
| + | |||
| + | |||
| + | |||
| + | ## Quick Reference | ||
| + | {{::swiftui_color_variable.jpg?nolink&400|}} | ||
| + | |||
| + | {{:swiftui_font_variable.png?nolink&600|}} | ||