Converting a Core Data Entity to a CKRecord for sharing using the UICloudSharingController with Seam3 - cloudkit

When trying to instantiate a CKRecord from my current datastore, I am unable to initialize it using Seam3. The core data stack and CloudKit are in perfect sync thanks to Seam3. I'm bulding a wishlist application that enables list sharing and I'm trying to implement this feature. Without a CKRecord to send to UICloudSharingController, I can't finish this feature.
I have tried converting to a CKRecord by hand and I don't know what I'm doing wrong.
func loadData() {
let fetchRequest = NSFetchRequest<List>(entityName: "List")
let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let result = try? dataController.viewContext.fetch(fetchRequest) {
lists = result
tableView.reloadData()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadData()
NotificationCenter.default.addObserver(forName: Notification.Name(rawValue: SMStoreNotification.SyncDidFinish), object: nil, queue: nil) { [weak self] notification in
if notification.userInfo != nil {
self?.dataController.smStore?.triggerSync(complete: true)
}
self?.dataController.viewContext.refreshAllObjects()
DispatchQueue.main.async {
self?.loadData()
}
}
}
These are the ways I am populating the tableView. I'm not sure how Seam3 works in the background to populate with the CloudKit Items. But its a seamless bridging between CloudKit and CoreData. Any help would be appreciated.... Thanks!

Related

How To Move To Spesific Screen iOS When User Tap Notification FCM

I made a notification, now my notification has appeared on iOS when I tap the notifications bagde the notification appears I want to switch to another View Controller page.
This my code in AppDelegate.swfit
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print("user data msg \(userInfo)")
guard let aps = userInfo["aps"] as? [String : AnyObject] else {
print("Error parsing aps")
return
}
print(aps)
if let alert = aps["alert"] as? String {
body = alert
} else if let alert = aps["alert"] as? [String : String] {
body = alert["body"]!
title = alert["title"]!
}
if let alert1 = aps["category"] as? String {
msgURL = alert1
}
print("Body\(body)")
print(title)
print(msgURL)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyBoard.instantiateViewController(withIdentifier: "register")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
}
}
But I got error: Cannot find 'present' in scope Where I should put my code for naviagtion when the user got a notification in Swift.
present() is a method on UIViewController, so you'll have to have a reference to a view controller that you can call that method on.
This can vary by the structure of your app -- especially if you're using a SceneDelegate, you have multiple windows, etc.
My suggestion is that you post a Notification that you can listen for in whatever has a reference to your top view controller. For example, instead of where you have present, you could do something like this:
let dataDict : [String : UIViewController] = ["vc": vc]
NotificationCenter.default.post(name: Notification.Name("ShowVC"), object: nil, userInfo: dataDict)
Then, assuming you're using a SceneDelegate, you could do this in your scene(_ scene: UIScene, willConnectTo) function:
NotificationCenter.default.publisher(for: Notification.Name("ShowVC"))
.compactMap {
$0.userInfo as? [String: UIViewController]
}
.sink {
guard let vc = $0["vc"] else {
return
}
window?.rootViewController?.present(vc, animated: true)
}.store(in: &cancelSet)
At the top of your file you'll need to import Combine and then your SceneDelegate will need a new property in order for the above code to work:
var cancelSet: Set<AnyCancellable> = []
Even if you aren't using a SceneDelegate, the basic concepts here should apply to other scenarios.
Note, however, that this is not a foolproof plan -- the root view controller has to be the visible view in order for this to work. This all may depend on your architecture. Search SO for "topmost view controller" to see plenty of discussion on this topic.

How to come back to previous scene and reload data?

I have two Scenes on Storyboard : One to show a list of items with plus button on the Bar to go to another Scene that has form to add a new item. I'm saving on Local Storage, in the saving function I want to come back to the previous page with the new data.
This is the saving function:
#IBAction func AddTrack(_ sender: Any) {
let item = TrackItem(context: PersistenceService.context)
item.kms = Int32(kmsField!.text!)!
item.liters = Float(litersField!.text!)!
item.date = textFieldPicker!.text!
PersistenceService.saveContext()
navigationController?.popViewController(animated: true)
self.TrackList.append(item)
self.tableView?.reloadData()
}
Knowing that I'm using this function in viewDidLoad() and viewDidAppear()
func GetData(){
let fetchRequest: NSFetchRequest<TrackItem> = NSFetchRequest<TrackItem>(entityName: "TrackItem")
do{
let TrackList = try PersistenceService.context.fetch(fetchRequest)
self.TrackList = TrackList
self.tableView?.reloadData()
}catch{
}
}
You can use NotificationCenter or Delegation (Depends on what you are trying to achieve)
Example NotificationCenter:
VC1, takes a photo:
buttonTapped / function () {
// Upload a Photo()
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.dismiss(animated: true, completion: nil)
}
}
VC2, shows all photos:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
}
#objc func loadList(notification: NSNotification) {
//get data, reload data, etc
}

Prepare for segue data not being loaded

I have a carousel and each carousel item contains a unique tableview. When you tap the add button it takes you to a new view controller so that you can give the tableview a name and associate items to display in each cell. Now when I hit the save button it performs the segue back to the carousel's viewcontroller. My prepareforsegue looks like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! BillSplittersViewController
let passedBill: NSManagedObject = bill as NSManagedObject
destinationVC.allBillSplitters = getUpdatedSplitters()
print(2)
print(destinationVC.allBillSplitters)
destinationVC.billName = billName
destinationVC.bill = passedBill
}
When the carousel view controller is displayed it doesn't show the added item in the carousel which loads from the allBillSplitters array. If I try reloading the carousel in viewdidload, viewwillappear or view willlayoutsubviews nothing changes. It does update if I do it in viewdidlayoutsubviews but you can see this happening which isn't great.
To debug I tried printing the array in prepareforsegue and then in viewdidload and it prints the array in prepareforsegue without the added item and the does the same thing in viewdidload -- but then it prints them again with the added item -- but I can't see it in the loaded view.
I'm using iCarousel if that helps. I am new to this and unsure what's going on so I don't know what code to use. I am using storyboards for both viewcontrollers and the segue is attached to the view and not the button itself. Any help would be great. Thanks!
More information:
#IBAction func saveButtonWasPressed() {
let managedContext = bill.managedObjectContext
if let splitter = splitter {
splitter.mutableSetValue(forKey: "items").removeAllObjects()
setBillSplitterValues(splitter)
setSelectedItemsToBillSplitter(splitter)
} else {
let entity = NSEntityDescription.entity(forEntityName: "BillSplitter", in: managedContext!)
let newBillSplitter = NSManagedObject(entity: entity!, insertInto: managedContext)
setBillSplitterValues(newBillSplitter)
setSelectedItemsToBillSplitter(newBillSplitter)
}
do {
try managedContext!.save()
}
catch let error as NSError {
print("Core Data save failed: \(error)")
}
self.performSegue(withIdentifier: "segueToBillSplitters", sender: self)
}
retrieving the new billSplitters:
func getUpdatedSplitters() -> [BillSplitter] {
var allBillSplitters = [BillSplitter]()
let managedContext = bill.managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "BillSplitter")
let predicate = NSPredicate(format: "ANY bills == %#", bill)
fetchRequest.predicate = predicate
do {
let results =
try managedContext!.fetch(fetchRequest)
allBillSplitters = results as! [BillSplitter]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
return allBillSplitters
}
So when pressing save, it either updates an existing 'billSplitter' to coredata or adds a new one, then calls performSegue. In prepareForSegue it then fetches all the 'billSplitters' from CoreData and passes them to the destination view controller.
I thought a NSManagedObjectContextDidSave observer might help if it called performSegue once the notification was received but nothing changed.
Update:
I have tried the following in my saveButtonWasPressed method. When the viewcontroller loads, it quickly shows the old carousel then flickers and shows the updated carousel. I'm not sure where to go from here.
#IBAction func saveButtonWasPressed() {
DispatchQueue.global(qos: .background).async { [weak weakSelf = self] in
let managedContext = weakSelf?.bill.managedObjectContext
if let splitter = weakSelf?.splitter {
splitter.mutableSetValue(forKey: "items").removeAllObjects()
weakSelf?.setBillSplitterValues(splitter)
weakSelf?.setSelectedItemsToBillSplitter(splitter)
} else {
let entity = NSEntityDescription.entity(forEntityName: "BillSplitter", in: managedContext!)
let newBillSplitter = NSManagedObject(entity: entity!, insertInto: managedContext)
weakSelf?.setBillSplitterValues(newBillSplitter)
weakSelf?.setSelectedItemsToBillSplitter(newBillSplitter)
}
do {
try managedContext!.save()
}
catch let error as NSError {
print("Core Data save failed: \(error)")
}
DispatchQueue.main.async { [weak weakSelf = self] in
guard let weakSelf = weakSelf else { return }
weakSelf.performSegue(withIdentifier: "segueToBillSplitters", sender: self)
}
}
}
I may not understand what you are asking exactly but my only input is that when I use prepare for segue I always use a segue identifier
var valueToPass
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if (segue.identifier == "yourSegue") {
let viewController = segue.destination as! ViewController
viewController.passedValue = valueToPass as String
}
Your declare your identifier in the storyboard, also make sure your segue is an action segue

Finding nil with optional managedObject, using CoreData and NSFetchedResultsController

I am trying to populate a collectionView with some CoreData. I'm programming in Swift and am using iOS 9. I have most everything hooked up correctly (to my knowledge), but I keep having a crash due to one line of code.
Here is the code from my viewController. The line that errors out with nil is "managedObjectContext: coreDataStack.context":
override func viewDidLoad() {
super.viewDidLoad()
//1
let fetchRequest = NSFetchRequest(entityName: "Family")
let firstNameSort =
NSSortDescriptor(key: "firstName", ascending: true)
fetchRequest.sortDescriptors = [firstNameSort]
//2
fetchedResultsController =
NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: coreDataStack.context,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultsController.delegate = CollectionViewFetchedResultsControllerDelegate(collectionView: familyCollectionView)
//3
do {
try fetchedResultsController.performFetch()
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
Below is the "CoreDataStack" object that I reference:
import Foundation
import CoreData
class CoreDataStack {
let modelName = "CoreDataModel"
lazy var context: NSManagedObjectContext = {
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = self.psc
return managedObjectContext
}()
private lazy var psc: NSPersistentStoreCoordinator = {
let coordinator = NSPersistentStoreCoordinator(
managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory
.URLByAppendingPathComponent(self.modelName)
do {
let options =
[NSMigratePersistentStoresAutomaticallyOption : true]
try coordinator.addPersistentStoreWithType(
NSSQLiteStoreType, configuration: nil, URL: url,
options: options)
} catch {
print("Error adding persistent store.")
}
return coordinator
}()
private lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = NSBundle.mainBundle()
.URLForResource(self.modelName,
withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
private lazy var applicationDocumentsDirectory: NSURL = {
let urls = NSFileManager.defaultManager().URLsForDirectory(
.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1]
}()
func saveContext () {
if context.hasChanges {
do {
try context.save()
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
abort()
}
}
}
}
Any ideas on why I'm picking up nil when I call that line?
The problem exists because self.coreDataStack was never assigned however it was accessed when the fetchedResultsController was defined.
To simply solve the problem, create and assign as instance of CoreDataStuck to your view controller's coreDataStack property before it is accessed:
self.coreDataStack = CoreDataStack()
I would like to stress the importance of managing the NSManagedObjectContext however. I believe it is a more common pattern to create an instance of CoreDataStack and pass a reference to it as you need it. For instance, you can create an instance of it within your Application Delegate and assign the reference to your first view controller with something like this:
lazy var cds = CoreDataStack()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSOBject: AnyObject?]?) -> Bool {
// Obtain a reference to your root view controller, this may have to be different if you use different types of controllers. This would be the View controller where you use the core data stack in your question though.
let vc = window!.rootViewController as! ViewController
vc.coreDataStack = self.cds // This will now ensure the property on your view controller is assigned and can be accessed.
return true
}
Later on in your app, I would then suggest passing this same reference around. You could do it by accessing the app delegate, personally I think it's cleaner to just pass along a reference through the prepare for segue method. I'll just imagine there's a SecondViewController class that exists in your app and that you're transitioning to it from the ViewController in your question:
class SecondViewController: UIViewController {
var cds: CoreDataStack!
}
I'll assume there's a segue between ViewController and SecondViewController with the name "next". In the ViewController file, define the prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "next" {
let secondVc = segue.destinationViewController as! SecondViewController
secondVc.cds = self.coreDataStack
}
}
I tried to stress the idea of keeping a reference to the instance of the core data stack that's first created, I think as you go along you'll find it more beneficial.

Reload data in swift, of a tableview every 30 seconds

So I have a table that takes some data from my data base and displays it in his cells. I made a refresh button that reloads my code so the user can be able to see the new entries that somebody added into the data base, when you tap it.
This is how the code of my refresh button looks like:
#IBAction func refreshButton(sender: AnyObject) {
textArray.removeAllObjects()
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Conversation", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
publicCloudDatabase?.performQuery(query, inZoneWithID: nil,
completionHandler: ({results, error in
if (error != nil) {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("Cloud Access Error",
message: error.localizedDescription)
}
} else {
if results.count > 0 {
var record = results[0] as! CKRecord
self.currentRecord = record
dispatch_async(dispatch_get_main_queue()) {
for x in results{
self.textArray.addObject(x.objectForKey("message") as! String)
self.tableView.reloadData()
}
}
} else {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("No Match Found",
message: "No record matching the address was found")
}
}
}
}))
}
I'd like to remove that button and make a refresh of the table every 30 seconds (in the background of the app - i don't want the user to be aware of it), it is that possible ? If yes, how can it be done? Thank you!
Using timers is one way you can achieve,
In ViewDidload
self.myTimer = NSTimer(timeInterval: 30.0, target: self, selector: "refresh", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(self.myTimer, forMode: NSDefaultRunLoopMode)
func refresh() {
// a refresh of the table
}
1.performQuery every 30s.
2.compare the different between old datas and new datas.
3.Insert the different part by using this Api
(void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
rather than reloadData