IGNORE MY PREVIOUS ANSWER!!!
The following is based upon:
a really good answer in StackOverflow to this question "SwiftUI View and @FetchRequest predicate with variable that can change", please refer https://stackoverflow.com/a/64200159/1883707.
a well written blog "Swift with Majid" specifically this article https://swiftwithmajid.com/2020/01/22/optimizing-views-in-swiftui-using-equatableview/ and in particular his explanation of "Diffing in SwiftUI".
This is the correct answer for two main reasons that I can currently identify...
This pattern breaks up the larger view into smaller views, which allows SwiftUI to better render and re-render.
This pattern breaks out the properties that are used in the SwiftUI diffing algorithm (as noted in the article by Majid) and therefore the fetch request calls are minimised and the predicate that I need for the @AppStorage property is injected into the child view. (I still can't quite get my head entirely around this, but the pattern works perfectly. If you can better explain it, I'd be grateful for an answer or comment.)
So here is the code...
struct Accounts: View {
@AppStorage("preference_displayArchived") var kDisplayArchived = true
var body: some View {
AccountsView(displayArchived: kDisplayArchived)
}
}
struct AccountsView: View {
let displayArchived: Bool
var body: some View {
AccountsList(accounts: SectionedFetchRequest(sectionIdentifier: \.sectionTypeName,
sortDescriptors: [
SortDescriptor(\.type?.name, order: .forward),
SortDescriptor(\.sortOrder, order: .forward)
],
predicate: displayArchived == true ? NSPredicate(value: true) : NSPredicate(format: "isArchived == %@", NSNumber(booleanLiteral: displayArchived)),
animation: .default),
displayArchived: displayArchived
)
}
}
struct AccountsList : View {
@SectionedFetchRequest var accounts: SectionedFetchResults<String, PTG_Account>
let displayArchived: Bool
@State private var searchText = String()
var query: Binding<String> {
Binding {
searchText
} set: { newValue in
searchText = newValue
let predicate01 = NSPredicate(format: "nameTensePresent CONTAINS[cd] %@", newValue)
let predicate02 = NSPredicate(format: "nameTensePast CONTAINS[cd] %@", newValue)
let predicateArchived = displayArchived ? NSPredicate(value: true) : NSPredicate(format: "isArchived == %@", NSNumber(booleanLiteral: displayArchived))
let predicateOr = NSCompoundPredicate(orPredicateWithSubpredicates: [predicate01, predicate02])
let predicateAll = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateOr, predicateArchived])
accounts.nsPredicate = newValue.isEmpty ? predicateArchived : predicateAll
}
}
var title: String {
return "Title For Your View"
}
var body: some View {
VStack(spacing: 0) {
ZStack(alignment: .bottom) {
ScrollViewReader { proxy in
List {
...
}
.onChange(of: displayArchived) { _ in
searchText = String()
}
}
ListFooter(countListRows: accounts.reduce(0, {$0 + $1.count}))
}
}
.searchable(text: query)
.navigationTitle(title)
.toolbar {
ToolbarItem(placement: .primaryAction) {
...
}
}
}
}
Where
@AppStorage("preference_displayArchived") var kDisplayArchived = true is the user setting to display archived files (in this case, in the Account List()).
PTG_Account is the class name for a core data entity Account.
.isArchived is the entity attribute of type Bool that is used to archive or unarchive an entity record (in this case, for the entity Account).
Post
Replies
Boosts
Views
Activity
I'm not that pleased with this solution but it works (on iOS only) and until I can find a more elegant method (that also works on macOS), this will have to suffice!
Noting that...
@AppStorage("preference_displayArchived") var kDisplayArchived = true
Using the same call to @SectionedFetchRequest in the question, I need to complete three tasks...
1 - Update the List when it appears and when the value to the preference changes to include a predicate that will effectively filter the SectionedFetchResults (in this case SectionedFetchResults<String, PTG_Event>):
var body: some View {
List() {
....
}
.onAppear() {
events.nsPredicate = predicateArchived
}
.onChange(of: kDisplayArchived) { _ in
events.nsPredicate = predicateArchived
}
}
2 - Add a computer property for predicateArchived:
var predicateArchived: NSPredicate {
kDisplayArchived == true ? NSPredicate(value: true) : NSPredicate(format: "isArchived == %@", NSNumber(booleanLiteral: kDisplayArchived))
}
3 - Finally I also have to update the search to ensure that this app preference is adopted during the search. Building on the code presented to us in WWDC21-10017 (in presentation skip to time 21:29):
@State private var searchText = String()
var query: Binding<String> {
Binding {
searchText
} set: { newValue in
searchText = newValue
let predicate01 = NSPredicate(format: "name CONTAINS[cd] %@", newValue)
let predicate02 = NSPredicate(format: "reference CONTAINS[cd] %@", newValue)
let predicateArchived = kDisplayArchived == true ? NSPredicate(value: true) : NSPredicate(format: "isArchived == %@", NSNumber(booleanLiteral: kDisplayArchived))
let predicateOr = NSCompoundPredicate(orPredicateWithSubpredicates: [predicate01, predicate02])
let predicateAll = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateArchived, predicateOr])
events.nsPredicate = newValue.isEmpty ? nil : predicateAll
}
}
NOTES
Known issue: after cancelling a search, the List refreshes without a predicate applied. Need to develop a workaround for this inconsistency.
It may be easier to present the detail in a sheet... rather than the three column layout you are currently working with?
Are you using the row selection option for Table?
From the documentation...
Supporting Selection in Tables -
To make rows of a table selectable, provide a binding to a selection variable.
Also sounds like it may be worth submitting a Feedback to Apple to describe the problems you are facing.
It may be easier to present the detail in a sheet... rather than the three column layout you are currently working with?
Are you using the row selection option for Table?
From the documentation...
**Supporting Selection in Tables**
To make rows of a table selectable, provide a binding to a selection variable.
Also sounds like it may be worth submitting a Feedback to Apple to describe the problems you are facing.
You've not really identified the context in which the error is appearing, however I can suggest that if it is relation to sorting, then you should replace KeyPathComparator with SortDescriptor.
For example... this doesn't work...
@State var sortOrder: [KeyPathComparator<ModelObject>] = [
.init(\.dateCommences, order: SortOrder.forward)
]
... however this does...
@State var sortOrder: [SortDescriptor<ModelObject>] = [
.init(\.dateCommences, order: SortOrder.forward)
]
where dateCommences is an example property of ModelObject.
The problem made me realise that I was force unwrapping an Optional, something I knew but didn't fully comprehend in this context. So the entity attribute sortOrder is of type "Integer 64" and checks "Use Scalar Type". On that last point, I want to work with Int64 in my code, not NSNumber.
Albeit that the UI isn't perfect, this works...
TextField("Sort Order",
text: Binding(
get: { String(account.sortOrder) },
set: { account.sortOrder = Int64($0.filter{ "0123456789".contains($0)}) ?? 0 }
)
)
I removed the force unwrap and instead "Coalesce using '??' to provide a default when the optional value contains 'nil'".
This doesn't work...
set: { account.sortOrder = Int64($0.filter{ "0123456789".contains($0)}) ?? nil }
... because "'nil' is not compatible with expected argument type 'Int64'".
When I traverse my way into the automatically generated property file for the Account entity, I find this line of code for sortOrder...
@NSManaged public var sortOrder: Int64
This file cannot edited so, using CodeGen = Class Definition and "Use Scalar Type", I am stuck with Int64.
So I guess I need to revisit my understanding of scalar types and how they work with Core Data CodeGen = Class Definition. It would seem that even though I check the "Optional" type option, the object graph and the automatically generated property file includes an Int64 - not Optional<Int64>.
As noted above, in my Core Data object graph, I check the "Use Scalar Type" for the entity attribute sortOrder of type "Integer 64". I do this for a number of reasons that I will not go into here - mainly because I spent a lot of time researching this years ago and made a decision then but acknowledge it may be outdated now - so this will force me to review my use of this option in the object graph.
FINALLY...
Because CodeGen = Class Definition creates an automatically generated property in my Account property file with an entity attribute of scalar type Int64 instead of the (non-scalar) NSNumber (standard use), I must assume that NumberFormatter requires a non-scalar type NSNumber to function.
Sure enough, when I uncheck the "Use Scalar Type" checkbox, NumberFormatter works perfectly for both macOS and iOS targets.
Conclusion: NumberFormatter currently (14 Aug 2021) works only with NSNumber.
Probably shouldn't have included a link for the FB. But I figured out why NumberFormatter is not working for me... In my Core Data object graph and using my example above, the entity sortOrder is an Integer 64 type with "Use Scalar Type" checked. So when CodeGen creates an automatically generated property for my Account class, it does so with an entity attribute of type Int64 (or perhaps more correctly int64_t) instead of the (non-scalar) NSNumber. So from this I can only assume that NumberFormatter requires a non-scalar type NSNumber. I have a few reasons why I use scalar types but I expect that this will prompt me to do two things - update my feedback AND consider whether I transition to using non-scalar types in my SwiftUI code.
As hinted in the error message, you're attempting to cast the type ServiceCategory in a manner that SwiftUI cannot manage.
The var sectionIdentifier is expecting a reference to a ServiceCategory as shown in this line
) var sectionedServices: SectionedFetchResults<ServiceCategory?, Service>
but instead you are passing the faulted object ID _NSCoreDataTaggedObjectID per your line
sectionIdentifier: \Service.serviceCategory,
Service.serviceCategory holds a reference to an entity instance... in your situation the ServiceCategory entity. If you place a break point in your code and query the value you'll probably see a reference to a record id for that entity, not the actual entity object.
Frankly I'd recommend that you change your approach a little write an extension on your Service entity and include a convenience method to grab this value...
extension Service: NSManagedObject {
@objc var serviceCategoryName: String {
return self.serviceCategory.name ?? "NO NAME"
}
}
then use this as follows...
@SectionedFetchRequest(
sectionIdentifier: \.serviceCategoryName,
sortDescriptors: [
SortDescriptor(\.active, order: .reverse),
SortDescriptor(\.displayText)
],
predicate: NSPredicate(format: "%K = %d", #keyPath(.active), true),
animation: .default
) var sectionedServices: SectionedFetchResults<String, Service>
Note the change from type ServiceCategory to type String.
and in the list...
ForEach(sectionedServices) { section in
Section(header: Text(section.id)) {
ForEach(section) { service in
Text(service.displayText ?? "")
}
}
}
Similar issue with Xcode 12 and target macOS 11.x
Universal app written in SwiftUI.
Suspect the cause of the issue was my late interruption of a build and run command. Not at all certain about this however.
Searched A LOT but no other suggestions worked, including deleting keychain items and build folders and creating new certificates and provisioning profiles.
The only solution that worked for me was to let Xcode automatically manage signing and to add the script detailed in the answer by @IndustrialDesigner
PS for clarity the script was added for the macOS target under "Build Phases" by clicking the + button and selecting "New Run Script Phase", then copying the script into the black script "box" under the Shell/bin/sh line.
@spellcasterdragonborn
Hah, Apple uses the underscore to format italic text, so when I type your name in here the underscores cause formatting chaos.
Yes, at this current moment in time, I agree with you - there is no need to use @StateObject.
So to be clear, at this current moment in time, I understand that ALL WE NEED to access Core Data features throughout our SwiftUI App is an NSManagedObjectContext.
To answer your question the long way round...
Apple does not let us edit our answers so when I wrote my original answer, this was all fairly new to me. If I could change the answer - as I have in my SwiftUI universal apps that use Core Data - I would change the following...
lazy var persistentContainer: NSPersistentContainer = {...}
Change to:
		private lazy var persistentContainer: NSPersistentContainer = {...}
My reason is that there are only three properties that I believe should be publicly available in my class implementation of PersistentStore... static let shared, ...and through this one line singleton...
var context, to use throughout the app; and
func save, for convenience.
If you check Apple's response to my forum question with the abstract title "New toolbar appearance in macOS 11", - https://developer.apple.com/forums/thread/650524 you can see their advice "and then your SwiftUI App could hold on to an instance of that (ed. custom) class in a variable." In their response, they also suggest I should watch the SwiftUI Data Essentials - https://developer.apple.com/videos/play/wwdc2020/10040/ WWDC session for inspiration. From my multiple viewings of this session, at the time I deducted that the hint was to use an @StateObject for my PersistentStore class, which is why I included it in my Core Data SwiftUI Xcode projects and in my original answer. It also made it easy for me to call save from the .onChange(of: scenePhase) view modifier.
In hindsight and following review of the template that Apple includes in more recent Xcode 12 betas, I was complicating their hint. (As per Apple's beta Core Data template) the instance of that persistent container custom class could be as simple as...
let persistenceController = PersistenceController.shared
or, per my previous answer...
let persistentStore = PersistentStore.shared
However, at this current moment in time, I would remove the @StateObject instance of my PersistentStore class. Not that I have done that yet in my SwiftUI projects (no reason, just not got around to it). It seems important for me to add subtext here - I am still developing an understanding of how @StateObject property wrapper operates in the App lifecycle, so in the future and with a better understanding, I might change this decision.
(PS: I just commented out @StateObject private var persistentStore = PersistentStore.shared in one project as a test and my iOS and macOS targets appear to build and run without issue. There seems to be a minor drop in memory usage, but this is speculative without taking steps to measure it properly.)
@JimDovey your quick example is very helpful thank you.
Any chance you might be able to explain how to include forward and backward navigation in your example.
In func makeUIView(context: Context) -> WKWebView {} I have attempted to include...
				view.allowsBackForwardNavigationGestures = true
Using your struct Display I can add buttons without too much trouble immediately beneath .navigationBarTitle...
.toolbar {
ToolbarItemGroup(placement: .navigation) {
Button(action: {
										// action to navigate back here
}) {
Image(systemName: "chevron.left")
}
Button(action: {
										// action to navigate forward here
}) {
Image(systemName: "chevron.right")
}
}
}
It is the code that is required to actually communicate between the SwiftUI View and its WebView: UIViewRepresentable that I'm struggling with.
I should note that my implementation is for a native macOS app written in SwiftUI.
It is not an iOS app for Mac built using Catalyst.
I understood the demo a little differently.
The Settings scene will automatically set up the standard Preferences command in the app menu and also give the window the correct style treatment. While the Settings scene will automatically set up the standard Preferences command in the app menu, it does not automatically set up the preferences window based on the iOS Settings.bundle. But it does style the window (whatever that means??). I understood that we must create the setting pane manually.
My implementation below, also using the new awesomely easy to use @AppStorage property wrapper that invalidates a view on change of the referenced UserDefaults.standard...
struct YourTestSettingsApp: App {
@SceneBuilder var body: some Scene {
WindowGroup {
ContentView()
}
				#if os(macOS)
Settings {
SettingsPane()
}
#endif
}
}
then the "Settings" view...
struct SettingsPane: View {
@AppStorage("preference_keyAsPerSettingBundleIdentifier") var kSetting = true
var body: some View {
Form {
Toggle("Perform some boolean Setting", isOn: $kSetting)
.help(kSetting ? "Undo that boolean Setting" : "Perform that boolean Setting")
}
.padding()
.frame(minWidth: 400)
}
}
This works for macOS 11 and iOS 14 (Xcode 12 beta 3)...
Take the Label out of the DatePicker - I've left it commented out here so its obvious to readers that the Label should be taken out of the DatePicker View.
Place the DatePicker in an HStack (or other ViewBuilder)
Add a Label for the DatePicker in the HStack
Use the .colorMultiplier view modifier to change the color of the DatePicker date and time "button".
In this particular example I am also changing the colour of the date and time "button" depending on whether the binding value in the date picker is nil.
let now = Date()
HStack {
Text("Date")
DatePicker(selection: Binding($observedObject.date, replacingNilWith: now)) {
// Text("Date")
// 	.foregroundColor(Color(.label))
}
.colorMultiply(observedObject.date == nil ? Color(.placeholderText) : Color(.label))
}
Also I'm using an extension to Binding in this example which is something I use throughout my core data projects.
public extension Binding where Value: Equatable {
init(_ source: Binding<Value?>, replacingNilWith nilProxy: Value) {
self.init(
get: { source.wrappedValue ?? nilProxy },
set: { newValue in
if newValue == nilProxy {
source.wrappedValue = nil
}
else {
source.wrappedValue = newValue
}
})
}
}
As always full credit to Alan Quatermain for this extension to Binding.
Should also mention that if you choose to adopt my answer as a solution and want it to build on both platforms, you'll need (as at Xcode 12.0 beta 3) an extension to Color...
extension Color {
static var foregroundLabel: Color {
#if os(macOS)
return Color(.labelColor)
#else
return Color(.label)
#endif
}
static var placeholderText: Color {
#if os(macOS)
return Color(.placeholderTextColor)
#else
return Color(.placeholderText)
#endif
}
}
Then use in code as...
TextEditor(text: Binding($objectDescription, replacingNilWith: ""))
...
.foregroundColor(Color.foregroundLabel)
...
Text(objectDescription ?? placeholder)
...
.foregroundColor(Color.placeholderText)
...