How to force FetchRequest update in SwiftUI
Issue #623
Listen to context changes notification and change SwiftUI View state
1 | let changes = [NSDeletedObjectsKey: ids] |
We need to actually use that State variable for it to have effect
Issue #623
Listen to context changes notification and change SwiftUI View state
1 | let changes = [NSDeletedObjectsKey: ids] |
We need to actually use that State variable for it to have effect
Issue #622
Read Implementing Batch Deletes
If the entities that are being deleted are not loaded into memory, there is no need to update your application after the NSBatchDeleteRequest has been executed. However, if you are deleting objects in the persistence layer and those entities are also in memory, it is important that you notify the application that the objects in memory are stale and need to be refreshed.
To do this, first make sure the resultType of the NSBatchDeleteRequest is set to NSBatchDeleteRequestResultType.resultTypeObjectIDs before the request is executed. When the request has completed successfully, the resulting NSPersistentStoreResult instance that is returned will have an array of NSManagedObjectID instances referenced in the result property. That array of NSManagedObjectID instances can then be used to update one or more NSManagedObjectContext instances.
1 | let fetchRequest: NSFetchRequest<NSFetchRequestResult> = Book.fetchRequest() |
Issue #621
Make subview that accepts FetchRequest
. Trigger search by setting property
1 | struct SideView: View { |
Issue #620
For NSWindow
having level
other than .normal
, need to override key and main property to allow TextField to be focusable
1 | class FocusWindow: NSWindow { |
Furthermore to customize TextField, consider using custom
1 | import SwiftUI |
Issue #618
Create custom Binding
1 | List { |
Issue #614
1 | struct MyTabView: View { |
Issue #613
1 | struct VOrH<Content>: View where Content: View { |
Issue #612
Use runModal
This method runs a modal event loop for the specified window synchronously. It displays the specified window, makes it key, starts the run loop, and processes events for that window. (You do not need to show the window yourself.) While the app is in that loop, it does not respond to any other events (including mouse, keyboard, or window-close events) unless they are associated with the window. It also does not perform any tasks (such as firing timers) that are not associated with the modal run loop. In other words, this method consumes only enough CPU time to process events and dispatch them to the action methods associated with the modal window.
Specify level
in windowDidBecomeKey
1 | let controller = SettingsWindowController() |
Issue #610
Set NSVisualEffectView
as contentView of NSWindow, and our main view as subview of it. Remember to set frame or autoresizing mask as non-direct content view does not get full size as the window
1 | let mainView = MainView() |
Updated at 2021-01-05 21:13:39
Issue #605
I use custom TextView
in a master detail application.
1 | import SwiftUI |
No matter which item user selects, textView always updates the first one
1 | struct Book { |
The fix is to pass selected object instead of using subscript
1 | struct MainView: View { |
And we need to save selectedBook
1 | class Store: ObservableObject { |
Updated at 2020-06-01 02:13:51
Issue #604
I see that the modifier needs to do something on the content, otherwise it is not getting called!
This logs on the modifier, when the View is created. A View won’t be recreated unless necessary
1 | struct LogModifier: ViewModifier { |
Another simpler way is to make an extension
Issue #599
Specify tag
1 | enum Authentication: Int, Codable { |
Updated at 2021-02-23 00:45:29
Issue #598
It’s hard to see any iOS app which don’t use UITableView or UICollectionView, as they are the basic and important foundation to represent data. UICollectionView
is very basic to use, yet a bit tedious for common use cases, but if we abstract over it, then it becomes super hard to customize. Every app is unique, and any attempt to wrap around UICollectionView will fail horribly. A sensable approach for a good abstraction is to make it super easy for normal cases, and easy to customize for advanced scenarios.
I’m always interested in how to make UICollectionView easier and fun to write and have curated many open sources here data source. Many of these data source libraries try to come up with totally different namings and complex paradigm which makes it hard to onboard, and many are hard to customize.
In its simplest form, what we want in a UICollectionView data source is cell = f(state)
, which means our cell representation is just a function of the state. We just want to set model to the cell, the correct cell, in a type safe manner.
The basic is to make a generic data source that sticks with a particular cell
1 | class DataSource<T>: NSObject { |
This works for basic usage, and we can create multiple DataSource for each kind of model. The problem is it’s hard to subclass DataSource as generic in Swift and inheritance for ObjcC NSObject don’t work well.
Seeing the problem with generic data source, I’ve tried another approach with Upstream where it’s easier to declare sections and models.
1 | let sections: [Section] = [ |
This uses the Adapter pattern and we need to handle AdapterDelegate
. To avoid the generic problem, this Adapter store items as Any
, so we need to type cast all the time.
1 | extension ProfileViewController: AdapterDelegate { |
The benefit is that we can easily subclass this Adapter manager to customize the behaviour, here is how to make accordion
1 | class AccordionManager<T>: Manager<T> { |
SwiftUI comes in iOS 13 with a very concise and easy to use syntax. SwiftUI has good diffing so we just need to update our models so the whole content will be diffed and rendered again.
1 | var body: some View { |
I built DeepDiff before and it was used by many people. Now I’m pleased to introduce Micro which is a SwiftU style with DeepDiff powered so it performs fast diffing whenever state changes.
With Micro
we can just use the familiar forEach
to declare Cell
, and the returned State
will tell DataSource
to update the UICollectionView
.
Every time state
is assigned, UICollectionView
will be fast diffed and reloaded. The only requirement is that your model should conform to DiffAware
with diffId
so DeepDiff knows how to diff for changes.
1 | let dataSource = DataSource(collectionView: collectionView) |
DataSource
is completely overridable, if you want to customize any methods, just subclass DataSource
, override methods and access its state.models
1 | class CustomDataSource: DataSource { |
In iOS 13, Apple adds Using Collection View Compositional Layouts and Diffable Data Sources which is very handy.
1 | func makeDataSource() -> UITableViewDiffableDataSource<Section, Contact> { |
This is iOS 13+ only, and the main components are the cellProvider
acting as cellForItemAtIndexPath
, and the snapshot for diffing. It also supports section.
Issue #595
SwiftUI uses ListCoreScrollView
and ListCoreClipView
under the hood. For now the workaround, is to avoid using List
use
Updated at 2020-08-14 07:26:27
Issue #594
1 | struct SelectFileView: View { |
Updated at 2020-07-09 01:45:55
Issue #592
Hard to customize
1 | Picker(selection: Binding<Bool>.constant(true), label: EmptyView()) { |
Use contentShape
to make whole button tappable.
Make custom Binding for our enum
1 | struct EnvironmentView: View { |
Issue #590
Use custom NSTextField
as it is hard to customize TextFieldStyle
1 | import SwiftUI |
Issue #517
Use ScrollView -> VStack -> ForEach -> Content
1 | struct SearchScreen: View { |
Issue #516
Suppose we have an array of SearchObject
, and user can enter search query into text
property.
1 | class SearchObject: ObservableObject { |
Although SearchObject
is class, when we use ForEach
, the changes to passed object won’t be reflected in our array and there is no reload trigger, we need to point to object in array directly, like
1 | struct SearchScreen: View { |
Issue #515
Use enumerated
and id: \.element.name
1 | struct CountriesView: View { |
Issue #513
A publisher that emits before the object has changed
Use workaround DispatchQueue
to wait another run loop to access newValue
1 | .onReceive(store.objectWillChange, perform: { |
Issue #511
1 | struct CountriesView: View { |
Issue #508
We need to use frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
. Note that order is important, and padding
should be first, and background
after frame
to apply color to the entire frame
1 | struct BooksScreen: View { |
Issue #507
View
extends to the bottom, but not to the notch. We need to add .edgesIgnoringSafeArea(.top)
to our TabView
to tell TabView
to extend all the way to the top.
Note that if we use edgesIgnoringSafeArea(.all)
then TabView
‘s bar will be dragged very down and broken.
1 | struct MainScreen: View { |
Issue #505
Make sure all String
are passed into Text
, not Optional<String>
Issue #502
Mutation
is used to mutate state synchronously. Action
is like intent, either from app or from user action. Action
maps to Mutation
in form of Publisher
to work with async action, similar to redux-observable
AnyReducer
is a type erasure that takes the reduce
function
1 | import Combine |
To use, conform to all the protocols. Also make typelias AppStore
in order to easy specify type in SwiftUI View
1 | import SwiftUI |
Use in SwiftUI
1 | struct RootScreen: View { |