Hello
I have an app that uses Core Data with CloudKit to store data. When I am in the app (using the app) and create some new data on an other device, the data is fetched in a few seconds and I see it immediately, however if I am not using the app and create some new data on an other device, I have to enter the app and then the data starts fetching, is there a way to fetch data even if I am not using the app.
Here is my core data stack:
import CoreData
import Combine
class PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init() {
container = NSPersistentCloudKitContainer(name: "CoreData")
guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "APP_GROUP_NAME")?.appendingPathComponent("CoreData.sqlite") else {
fatalError("Shared file container could not be created.")
}
let storeDescription = NSPersistentStoreDescription(url: fileContainer)
storeDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
storeDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
storeDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "Container_Identifier")
container.persistentStoreDescriptions = [storeDescription]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
}
}
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hello
I have this Core Data stack and I have an observer to observe NSPersistentStoreRemoteChange, I would like to filter changes that the user has made to call the mergePersistentHistoryChanges() just when needed, I think it has to be done in the fetchPersistentHistoryTransactionsAndChanges() function but I don't know how to do it. Can you help me. Thank You
Here is my Core Data Stack:
class PersistenceController {
static let shared = PersistenceController()
private var notificationToken: NSObjectProtocol?
init() {
notificationToken = NotificationCenter.default.addObserver(forName: .NSPersistentStoreRemoteChange, object: nil, queue: nil) { note in
Task {
await self.fetchPersistentHistory()
}
}
}
deinit {
if let observer = notificationToken {
NotificationCenter.default.removeObserver(observer)
}
}
private var lastToken: NSPersistentHistoryToken?
/// A persistent container to set up the Core Data stack.
lazy var container: NSPersistentCloudKitContainer = {
let fileContainer = URL.storeURL(for: "group name", databaseName: "CoreData")
let container = NSPersistentCloudKitContainer(name: "CoreData")
let defaultDirectoryURL = NSPersistentContainer.defaultDirectoryURL()
let localStoreURL = defaultDirectoryURL.appendingPathComponent("Local.sqlite")
let localStoreDescription = NSPersistentStoreDescription(url: localStoreURL)
localStoreDescription.configuration = "Local"
// Create a store description for a CloudKit-backed local store
let cloudStoreDescription = NSPersistentStoreDescription(url: fileContainer)
cloudStoreDescription.configuration = "Cloud"
// Set the container options on the cloud store
cloudStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "containerIdentifier")
cloudStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
cloudStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.persistentStoreDescriptions = [cloudStoreDescription, localStoreDescription]
container.loadPersistentStores { storeDescription, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
// This sample refreshes UI by consuming store changes via persistent history tracking.
/// - Tag: viewContextMergeParentChanges
container.viewContext.automaticallyMergesChangesFromParent = false
container.viewContext.name = "viewContext"
container.viewContext.transactionAuthor = "User"
/// - Tag: viewContextMergePolicy
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.undoManager = nil
container.viewContext.shouldDeleteInaccessibleFaults = true
return container
}()
private func newTaskContext() -> NSManagedObjectContext {
// Create a private queue context.
/// - Tag: newBackgroundContext
let taskContext = container.newBackgroundContext()
taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
// Set unused undoManager to nil for macOS (it is nil by default on iOS)
// to reduce resource requirements.
taskContext.undoManager = nil
return taskContext
}
func save() {
if self.container.viewContext.hasChanges {
do {
try self.container.viewContext.save()
} catch {
print(Errors.errorSaving)
}
}
}
func fetchPersistentHistory() async {
do {
try await fetchPersistentHistoryTransactionsAndChanges()
} catch {
print(Errors.fetchPersistentHistory)
}
}
private func fetchPersistentHistoryTransactionsAndChanges() async throws {
let taskContext = newTaskContext()
taskContext.name = "persistentHistoryContext"
try await taskContext.perform {
let changeRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: self.lastToken)
let historyResult = try taskContext.execute(changeRequest) as? NSPersistentHistoryResult
if let history = historyResult?.result as? [NSPersistentHistoryTransaction],
!history.isEmpty {
self.mergePersistentHistoryChanges(from: history)
return
}
throw Errors.fetchPersistentHistoryTransactionsAndChanges
}
}
private func mergePersistentHistoryChanges(from history: [NSPersistentHistoryTransaction]) {
let viewContext = container.viewContext
viewContext.perform {
for transaction in history {
print("Merged by func mergePersistentHistoryChanges ")
viewContext.mergeChanges(fromContextDidSave: transaction.objectIDNotification())
self.lastToken = transaction.token
}
}
}
}
Hello
I'm trying to schedule a local notification to fire every day, at a specific time, but from tomorrow.
e.g. "Trigger a notification every day at 2 pm, from tomorrow"
This is how I set up my schedule function.
func scheduleNotifications(date: Date, identfier: String, after: Bool) {
let content = UNMutableNotificationContent()
content.title = "App"
content.body = "Test"
content.sound = .default
content.userInfo = ["Hour": Int(hourFormatter.string(from: date)) ?? 0]
let afterDay = Calendar.current.date(byAdding: .day, value: after ? 1 : 0, to: Date())
var components = Calendar.current.dateComponents([.hour, .minute], from: afterDay!)
components.hour = Int(hourFormatter.string(from: date)) ?? 0
components.minute = Int(minuteFormatter.string(from: date)) ?? 0
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)
let request = UNNotificationRequest(identifier: identfier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
}
Hello
How do I disable a notification for a day if it is set within one hour and the user has completed a task.
This is my function to set up local notifications:
import UserNotifications
func scheduleNotifications(date: Date, identfier: String) {
let content = UNMutableNotificationContent()
content.title = "App name"
content.body = "Message."
content.sound = .default
content.userInfo = ["Hour": timeFormatter.string(from: date)]
var dateComponents = DateComponents()
dateComponents.hour = Int(hourFormatter.string(from: date)) ?? 0
dateComponents.minute = Int(minuteFormatter.string(from: date)) ?? 0
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: identfier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
}
Hello
I am making a To-Do list app where I use CoreData and CloudKit, the problem is that when I added this line of code
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: fileContainer.appendingPathComponent("MJ.sqlite"))]
to the PersistenceController, iCloud syncing stopped working. (I need that line of code in order to permit to extensions to access the CoreData database)
Any idea to solve the problem?
This is all the PersistenceController code
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "MJ")
guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.name") else {
fatalError("Shared file container could not be created.")
}
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: fileContainer.appendingPathComponent("MJ.sqlite"))]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
}
}
Thank you!
Hello
I created a simple SwiftUI app with Core Data and I want to be able to add data via the shortcuts app, I created a shortcut that takes some text as input and returns it in uppercase and when I run the shortcut in the shortcuts app, it works, however when I added an "add" function (to save data in the Core Data database) to the intent handle function, and I run it again nothing is saved in the app, here is the code:
class MakeUppercaseIntentHandler: NSObject, MakeUppercaseIntentHandling {
let persistenceController = PersistenceController()
func handle(intent: MakeUppercaseIntent, completion: @escaping (MakeUppercaseIntentResponse) -> Void) {
if let inputText = intent.text {
let uppercaseText = inputText.uppercased()
completion(MakeUppercaseIntentResponse.success(result: add(text: uppercaseText)))
} else {
completion(MakeUppercaseIntentResponse.failure(error: "The text entred is invalid"))
}
}
func resolveText(for intent: MakeUppercaseIntent, with completion: @escaping (MakeUppercaseTextResolutionResult) -> Void) {
if let text = intent.text, !text.isEmpty {
completion(MakeUppercaseTextResolutionResult.success(with: text))
} else {
completion(MakeUppercaseTextResolutionResult.unsupported(forReason: .noText))
}
}
func add(text: String) -> String{
let newItem = Item(context: persistenceController.container.viewContext)
newItem.text = text
do {
try persistenceController.container.viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return text
}
}
Thank You
Hello
I created a @propertyWrapper to limit the number a variable can reach. I tried it in a SwiftUI view with a button that increases the value of the variable and it works, the variable stops at the maximum number set in the initializer. However if I try it with a Textflied it doesn't work, if I insert a higher number than the one set nothing happens, it makes me do it. How can I solve this problem, I know the problem has to do with Binding but I don't know exactly what it is, here is the code:
import SwiftUI
struct ContentView: View {
@Maximum(maximum: 12) var quantity: Int
var body: some View {
NavigationView{
Form{
TextField("", value: $quantity, format: .number, prompt: Text("Pizza").foregroundColor(.red))
Button {
quantity += 1
} label: {
Text("\(quantity)")
}
}
}
}
}
@propertyWrapper
struct Maximum<T: Comparable> where T: Numeric {
@State private var number: T = 0
var max: T
var wrappedValue: T {
get { number }
nonmutating set { number = min(newValue, max) }
}
var projectedValue: Binding<T> {
Binding(
get: { wrappedValue },
set: { wrappedValue = $0 }
)
}
init(maximum: T){
max = maximum
}
}
extension Maximum: DynamicProperty {
}
Thank You for your time
Hello
I am using CoreData in my App and want to save a single Picker value. I could create an entity with an Integer attribute and always edit the first object in the EntityList. But that doesn't sound like a clean solution.
The Picker is placed inside a settings view and has to be synced with iCloud.
Is there a simple way to do this?
Thank You
Hello
If I have this array:
var objects = [
Object(value: 100, date: Date(), imageTemplate: "30"),
Object(value: 200, date: Date(), imageTemplate: "20"),
Object(value: 400, date: Date() + 84000, imageTemplate: "10")
]
How can I count how many different dates are in the array using NSPredicate?
In this case it should return 2.
Thank You!
Hello
I am using a SwiftUI @FetchRequest to displays Core Data items, one of the properties of the entity is date, and I want to filter items by today's Date, this is the @FetchRequest:
@FetchRequest(
entity: Book.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Book.date, ascending: true)],
predicate: NSPredicate(format: "date == %@"),
animation: .default)
var books: FetchedResults<Book>
How do I complete the NSPredicate to make it work? (I know that there are no arguments in the predicate yet)
Thank You!
Hello
@FetchRequest(
entity: Book.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Book.entity.date, ascending: true)]
) var books: FetchedResults<Book.entity>
How can I get notified when anything in book changes without using
.onRecevie(books.publisher){ _ in
....
}
And is there any way to use .onChange modifier with books?
Thank You!
Hello
@FetchRequest(
entity: Book(),
sortDescriptors: []
) var books: FetchedResults<Book>
How can I get notified when books changes?
I was thinking about putting this...
.onReceive(NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave), perform: { _ in})
... in my main View but this would notify me if anything gets saved and that is not what I want. I want to get notified just if a certain FetchRequest changes (in this case books)?
And in another thread I was told to use...
.onRecevie(books.publisher){ _ in
....
}
...however using this code publishes each member of the sequence as a separate element, I want it to publish just once?
Thank You!
Hello
In my app I have a list of items in a ForEach and at the end of the ForEach there is a .onDelete. (This is in a View that I called AllHistoryView)
The items are represented by a CoreData NSManagedObject that I called LifetimeInputs with 3 properties:
date: Date, imageTemplate: String, valori: Double
The problem is that, when I add (in a different View) an item with the date of tomorrow and then I add another to item with the date of today and then go to the AllHistoryView where there are all the items and delete slowly the item with date of tomorrow (especially at the end of the swipe action) instead of deleting the item that I swiped it deletes the on before it, how can I solve this problem?
Here is the AllHistoryView code:
import SwiftUI
import CoreData
struct AllHistoryView: View {
@Environment(\.managedObjectContext) var viewContext
@FetchRequest(entity: LifetimeInputs.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \LifetimeInputs.date, ascending: true)], animation: .default) var lifetimeInputsModel: FetchedResults<LifetimeInputs>
@State private var text: String = ""
@State private var ascending: Bool = true
@State var sortDescriptor: NSSortDescriptor = NSSortDescriptor(keyPath: \LifetimeInputs.date, ascending: true)
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var size: CGFloat{
if UIDevice.current.userInterfaceIdiom == .phone {
switch dynamicTypeSize {
case .xSmall: return 11
case .small: return 13
case .medium: return 15
case .large: return 17
case .xLarge: return 19
case .xxLarge: return 21
case .xxxLarge: return 23
default: return 23
}
} else {
switch dynamicTypeSize {
case .xSmall: return 13
case .small: return 15
case .medium: return 17
case .large: return 19
case .xLarge: return 21
case .xxLarge: return 23
case .xxxLarge: return 25
case .accessibility1: return 27
case .accessibility2: return 29
default: return 29
}
}
}
var body: some View {
theView()
}
@ViewBuilder
func theView() -> some View{
NavigationView{
if !lifetimeInputsModel.isEmpty{
List{
SectionList(sortDescripter: sortDescriptor, text: $text)
.environment(\.managedObjectContext, viewContext)
.onChange(of: ascending, perform: { _ in
if ascending {
withAnimation {
sortDescriptor = NSSortDescriptor(keyPath: \LifetimeInputs.date, ascending: true)
}
} else {
withAnimation {
sortDescriptor = NSSortDescriptor(keyPath: \LifetimeInputs.date, ascending: false)
}
}
})
}
.searchable(text: $text, placement: .navigationBarDrawer, prompt: "Quantity or date".localized())
.navigationBarTitle("History", displayMode: .inline)
.toolbar{
ToolbarItem(placement: .automatic) {
Image(systemName: "arrow.up.arrow.down.circle")
.foregroundColor(.primary)
.font(.system(size: size))
.rotation3DEffect(.degrees(ascending ? 0 : 180), axis: (x: 1, y: 0, z: 0))
.opacity(0.5)
.onTapGesture {
ascending.toggle()
}
}
}
} else{
VStack{
Text("No Data".localized())
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.secondary)
}
.padding(.bottom)
.navigationBarTitle("History", displayMode: .inline)
}
}
}
}
struct SectionList: View {
@Environment(\.managedObjectContext) var viewContext
@FetchRequest var lifetimeInputsModel: FetchedResults<LifetimeInputs>
@Binding var text: String
init(sortDescripter: NSSortDescriptor, text: Binding<String>) {
let request: NSFetchRequest<LifetimeInputs> = LifetimeInputs.fetchRequest()
request.sortDescriptors = [sortDescripter]
_lifetimeInputsModel = FetchRequest<LifetimeInputs>(fetchRequest: request)
self._text = text
}
@FetchRequest(entity: Limit.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Limit.date, ascending: true)], animation: .default) var limit: FetchedResults<Limit>
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var size: CGFloat{
if UIDevice.current.userInterfaceIdiom == .phone {
switch dynamicTypeSize {
case .xSmall: return 11
case .small: return 13
case .medium: return 15
case .large: return 17
case .xLarge: return 19
case .xxLarge: return 21
case .xxxLarge: return 23
default: return 23
}
} else {
switch dynamicTypeSize {
case .xSmall: return 13
case .small: return 15
case .medium: return 17
case .large: return 19
case .xLarge: return 21
case .xxLarge: return 23
case .xxxLarge: return 25
case .accessibility1: return 27
case .accessibility2: return 29
default: return 29
}
}
}
@StateObject var lifeTimeInputsViewModel = LifeTimeInputsViewModel()
var body: some View {
Section{
ForEach(lifetimeInputsModel.filter { text.isEmpty || "\($0)".contains(text) }){ lifetimeInputs in
HStack{
Text("\(lifetimeInputs.valori, specifier: format(unita: !limit.isEmpty ? limit[0].unita ?? ml : ml)) \(!limit.isEmpty ? limit[0].unita ?? ml: ml)")
.font(.system(size: size))
Spacer()
Text("\(dateFormatter.string(from: lifetimeInputs.date ?? Date()))")
.font(.system(size: size))
}
}
.onDelete(perform: { offsets in
lifeTimeInputsViewModel.deleteItems(offsets: offsets)
})
} header: {
Text("History".localized()).font(.system(size: size - 4))
}
}
}
Thank You very much!
Hello
I created a custom shape in SwiftUI and I am trying to rotate it around a circle, but it works just on the top part of the circle, can you help me make it rotate exactly around the circle?
(And also can I get the same effect using radians? How?)
Here is the code:
import SwiftUI
struct MyGameView: View {
@State private var degress: Double = 0
let timer = Timer.publish(every: 0.05, on: .main, in: .common).autoconnect()
var body: some View {
VStack{
ZStack{
Circle()
.frame(width: 80)
ZStack{
Circle()
.stroke(lineWidth: 1)
.frame(width: 300)
BallonShape()
.scaledToFit()
.scaleEffect(0.2)
.foregroundColor(.red)
.rotationEffect(.degrees(degress), anchor: .bottom)
.offset(x: 0, y: -170)
}
}
}
.onReceive(timer) { input in
withAnimation(.easeIn(duration: 0.05).speed(10)){
degress += 1
}
}
}
}
struct BallonShape: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.midX, y: (rect.maxY + rect.midY) / 2))
path.addCurve(to: CGPoint(x: rect.midX, y: rect.minY), control1: CGPoint(x: (rect.midX + rect.minX) / 2, y: rect.minY), control2: CGPoint(x: (rect.midX + rect.minX) / 2, y: rect.minY))
path.addCurve(to: CGPoint(x: rect.midX, y: (rect.maxY + rect.midY) / 2), control1: CGPoint(x: (rect.midX + rect.maxX) / 2, y: rect.minY), control2: CGPoint(x: (rect.midX + rect.maxX) / 2, y: rect.minY))
}
}
}
Thank You very much!
Hello
I wanted to know why when I print(Date()) the date is printed but two hours before?
Example
If I print(Date()) and the actual date is 2021-09-10 22:33:41 +0000, it prints 2021-09-10 20:33:41 +0000 instead of 2021-09-10 22:33:41 +0000.
Why does this happen?
Thank You!