I have implemented sharing of items from my Core Data store, using the NSPersistentCloudKitContainer api, with limited success. The objects I share (a TripsLog) have child objects (Trip), which appear on participants' devices. I've implemented more or less a direct copy of Apple's example code, except using my own core data entities.
The problem is that any new trips I create on participants' devices don't appear in the owner's database, whereas any I create on the owner's device appear for all participants, updating their displays pretty much instantly. If I edit any of the original trips on a participant's device, the changes are successfully shared, it's just the new ones which are problematic.
I'm getting a lot of noise in the console -
"Attempt to map database failed: permission was denied. This attempt will not be retried"
"Permission Failure" (10/2007); server message = "Invalid bundle ID for container"
etc
which really aren't helpful. The bundle ID & containers are set up properly and it all works fine for syncing across a single user's devices.
Although my code is more-or-less just the same as Apple's, except for the container identifier & the Core Data entities, here's most of the relevant stuff -
// sharing protocol
protocol CloudKitSharable: NSManagedObject {
static var entityName: String { get }
var identifier: String { get }
var sharedTitle: String { get }
var sharedSubject: String? { get }
var thumbnailImage: UIImage? { get }
}
class PersistenceController: NSObject {
enum CoreDataError: Error {
case modelURLNotFound(forResourceName: String)
case modelLoadingFailed(forURL: URL)
}
static let shared = PersistenceController()
private static let containerIdentifier = "iCloud.com.containerIdentifier"
private var _privatePersistentStore: NSPersistentStore?
var privatePersistentStore: NSPersistentStore {
return _privatePersistentStore!
}
private var _sharedPersistentStore: NSPersistentStore?
var sharedPersistentStore: NSPersistentStore {
return _sharedPersistentStore!
}
lazy var cloudKitContainer: CKContainer = {
return CKContainer(identifier: Self.containerIdentifier)
}()
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container: NSPersistentCloudKitContainer = try! mainDatabaseContainer()
guard let localDatabaseURL = localDatabaseURL, let cloudDatabaseURL = cloudDatabaseURL else {
fatalError("#\(#function): Failed to get local database URLs")
}
// Set up the database which will sync over the cloud
let cloudStoreDescription = NSPersistentStoreDescription(url: cloudDatabaseURL)
cloudStoreDescription.configuration = "Cloud"
cloudStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
setupStoreDescription(cloudStoreDescription)
let cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: Self.containerIdentifier)
cloudKitContainerOptions.databaseScope = .private
cloudStoreDescription.cloudKitContainerOptions = cloudKitContainerOptions
// Setting up for sharing
let sharedStoreDescription = cloudStoreDescription.copy() as! NSPersistentStoreDescription
sharedStoreDescription.url = sharedDatabaseURL
let sharedStoreOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: Self.containerIdentifier)
sharedStoreOptions.databaseScope = .shared
sharedStoreDescription.cloudKitContainerOptions = sharedStoreOptions
// Set up the stuff which we don't want to sync to the cloud
let localStoreDescription = NSPersistentStoreDescription(url: localDatabaseURL)
localStoreDescription.configuration = "Local"
setupStoreDescription(localStoreDescription)
// finish setting up the container
container.persistentStoreDescriptions = [
cloudStoreDescription,
localStoreDescription,
sharedStoreDescription
]
loadPersistentStores(for: container)
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.transactionAuthor = TransactionAuthor.app
/**
Automatically merge the changes from other contexts.
*/
container.viewContext.automaticallyMergesChangesFromParent = true
/**
Pin the viewContext to the current generation token and set it to keep itself up-to-date with local changes.
*/
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("#\(#function): Failed to pin viewContext to the current generation:\(error)")
}
/**
Observe the following notifications:
- The remote change notifications from container.persistentStoreCoordinator.
- The .NSManagedObjectContextDidSave notifications from any context.
- The event change notifications from the container.
*/
NotificationCenter.default.addObserver(self, selector: #selector(storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange,
object: container.persistentStoreCoordinator)
NotificationCenter.default.addObserver(self, selector: #selector(containerEventChanged(_:)),
name: NSPersistentCloudKitContainer.eventChangedNotification,
object: container)
return container
}()
func mergeTransactions(_ transactions: [NSPersistentHistoryTransaction], to context: NSManagedObjectContext) {
context.perform {
for transaction in transactions {
context.mergeChanges(fromContextDidSave: transaction.objectIDNotification())
}
}
}
private var cloudDatabaseURL: URL? {
return UIApplication.applicationSupportDirectory?.appendingPathComponent("CloudDatabase.sqlite")
}
private var localDatabaseURL: URL? {
return UIApplication.applicationSupportDirectory?.appendingPathComponent("Database.sqlite")
}
private var sharedDatabaseURL: URL? {
return UIApplication.applicationSupportDirectory?.appendingPathComponent("Shared.sqlite")
}
private func setupStoreDescription(_ description: NSPersistentStoreDescription) {
description.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.shouldInferMappingModelAutomatically = true
}
private func model(name: String) throws -> NSManagedObjectModel {
return try loadModel(name: name, bundle: Bundle.main)
}
private func loadModel(name: String, bundle: Bundle) throws -> NSManagedObjectModel {
guard let modelURL = bundle.url(forResource: name, withExtension: "momd") else {
throw CoreDataError.modelURLNotFound(forResourceName: name)
}
guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
throw CoreDataError.modelLoadingFailed(forURL: modelURL)
}
return model
}
private func mainDatabaseContainer() throws -> NSPersistentCloudKitContainer {
return NSPersistentCloudKitContainer(name: "Database", managedObjectModel: try model(name: "Database"))
}
private func locationsContainer() throws -> NSPersistentContainer {
return NSPersistentContainer(name: "Locations", managedObjectModel: try model(name: "Locations"))
}
private func loadPersistentStores(for container: NSPersistentContainer) {
container.loadPersistentStores { [unowned self] storeDescription, error in
if let error = error {
fatalError("#\(#function): Failed to load persistent stores:\(error)")
} else {
print("Database store ok: ", storeDescription)
if let containerOptions = storeDescription.cloudKitContainerOptions, let url = self.sharedDatabaseURL {
if containerOptions.databaseScope == .shared {
let sharedStore = container.persistentStoreCoordinator.persistentStore(for: url)
self._sharedPersistentStore = sharedStore
}
} else if let url = self.cloudDatabaseURL {
let privateStore = container.persistentStoreCoordinator.persistentStore(for: url)
self._privatePersistentStore = privateStore
}
}
}
}
/**
An operation queue for handling history-processing tasks: watching changes, deduplicating tags, and triggering UI updates, if needed.
*/
lazy var historyQueue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return queue
}()
}
// Sharing extensions
extension PersistenceController {
func presentCloudSharingController<T: CloudKitSharable>(for item: T) {
/**
Grab the share if the item is already shared.
*/
var itemToShare: CKShare?
if let shareSet = try? persistentContainer.fetchShares(matching: [item.objectID]), let (_, share) = shareSet.first {
itemToShare = share
}
let sharingController: UICloudSharingController
if let itemToShare = itemToShare {
sharingController = UICloudSharingController(share: itemToShare, container: cloudKitContainer)
} else {
sharingController = newSharingController(for: item)
}
sharingController.delegate = self
/**
Setting the presentation style to .formSheet so there's no need to specify sourceView, sourceItem, or sourceRect.
*/
if let viewController = rootViewController {
sharingController.modalPresentationStyle = .formSheet
viewController.present(sharingController, animated: true)
}
}
func presentCloudSharingController(share: CKShare) {
let sharingController = UICloudSharingController(share: share, container: cloudKitContainer)
sharingController.delegate = self
/**
Setting the presentation style to .formSheet so there's no need to specify sourceView, sourceItem, or sourceRect.
*/
if let viewController = rootViewController {
sharingController.modalPresentationStyle = .formSheet
viewController.present(sharingController, animated: true)
}
}
private func newSharingController<T: CloudKitSharable>(for unsharedItem: T) -> UICloudSharingController {
return UICloudSharingController { (_, completion: #escaping (CKShare?, CKContainer?, Error?) -> Void) in
/**
The app doesn't specify a share intentionally, so Core Data creates a new share (zone).
CloudKit has a limit on how many zones a database can have, so this app provides an option for users to use an existing share.
If the share's publicPermission is CKShareParticipantPermissionNone, only private participants can accept the share.
Private participants mean the participants an app adds to a share by calling CKShare.addParticipant.
If the share is more permissive, and is, therefore, a public share, anyone with the shareURL can accept it,
or self-add themselves to it.
The default value of publicPermission is CKShare.ParticipantPermission.none.
*/
self.persistentContainer.share([unsharedItem], to: nil) { objectIDs, share, container, error in
if let share = share {
self.configure(share: share, with: unsharedItem)
}
completion(share, container, error)
}
}
}
private var rootViewController: UIViewController? {
for scene in UIApplication.shared.connectedScenes {
if scene.activationState == .foregroundActive,
let sceneDelegate = (scene as? UIWindowScene)?.delegate as? UIWindowSceneDelegate,
let window = sceneDelegate.window {
return window?.rootViewController
}
}
print("\(#function): Failed to retrieve the window's root view controller.")
return nil
}
}
extension PersistenceController: UICloudSharingControllerDelegate {
/**
CloudKit triggers the delegate method in two cases:
- An owner stops sharing a share.
- A participant removes themselves from a share by tapping the Remove Me button in UICloudSharingController.
After stopping the sharing, purge the zone or just wait for an import to update the local store.
This sample chooses to purge the zone to avoid stale UI. That triggers a "zone not found" error because UICloudSharingController
deletes the zone, but the error doesn't really matter in this context.
Purging the zone has a caveat:
- When sharing an object from the owner side, Core Data moves the object to the shared zone.
- When calling purgeObjectsAndRecordsInZone, Core Data removes all the objects and records in the zone.
To keep the objects, deep copy the object graph you want to keep and make sure no object in the new graph is associated with any share.
The purge API posts an NSPersistentStoreRemoteChange notification after finishing its job, so observe the notification to update
the UI, if necessary.
*/
func cloudSharingControllerDidStopSharing(_ csc: UICloudSharingController) {
if let share = csc.share {
purgeObjectsAndRecords(with: share)
}
}
func cloudSharingControllerDidSaveShare(_ csc: UICloudSharingController) {
if let share = csc.share, let persistentStore = share.persistentStore {
persistentContainer.persistUpdatedShare(share, in: persistentStore) { (share, error) in
if let error = error {
print("\(#function): Failed to persist updated share: \(error)")
}
}
}
}
func cloudSharingController(_ csc: UICloudSharingController, failedToSaveShareWithError error: Error) {
print("\(#function): Failed to save a share: \(error)")
}
func itemTitle(for csc: UICloudSharingController) -> String? {
return csc.share?.title ?? "Airframe Logbook"
}
}
extension PersistenceController {
func shareObject<T: CloudKitSharable>(_ unsharedObject: T, to existingShare: CKShare?, completionHandler: ((_ share: CKShare?, _ error: Error?) -> Void)? = nil) {
persistentContainer.share([unsharedObject], to: existingShare) { (objectIDs, share, container, error) in
guard error == nil, let share = share else {
print("\(#function): Failed to share an object: \(error!))")
completionHandler?(share, error)
return
}
/**
Deduplicate tags, if necessary, because adding a photo to an existing share moves the whole object graph to the associated
record zone, which can lead to duplicated tags.
*/
if existingShare != nil {
/*
if let tagObjectIDs = objectIDs?.filter({ $0.entity.name == "Tag" }), !tagObjectIDs.isEmpty {
self.deduplicateAndWait(tagObjectIDs: Array(tagObjectIDs))
}
*/
} else {
self.configure(share: share, with: unsharedObject)
}
/**
Synchronize the changes on the share to the private persistent store.
*/
self.persistentContainer.persistUpdatedShare(share, in: self.privatePersistentStore) { (share, error) in
if let error = error {
print("\(#function): Failed to persist updated share: \(error)")
}
completionHandler?(share, error)
}
}
}
/**
Delete the Core Data objects and the records in the CloudKit record zone associated with the share.
*/
func purgeObjectsAndRecords(with share: CKShare, in persistentStore: NSPersistentStore? = nil) {
guard let store = (persistentStore ?? share.persistentStore) else {
print("\(#function): Failed to find the persistent store for share. \(share))")
return
}
persistentContainer.purgeObjectsAndRecordsInZone(with: share.recordID.zoneID, in: store) { (zoneID, error) in
if let error = error {
print("\(#function): Failed to purge objects and records: \(error)")
}
}
}
func existingShare(for item: NSManagedObject) -> CKShare? {
if let shareSet = try? persistentContainer.fetchShares(matching: [item.objectID]),
let (_, share) = shareSet.first {
return share
}
return nil
}
func share(with title: String) -> CKShare? {
let stores = [privatePersistentStore, sharedPersistentStore]
let shares = try? persistentContainer.fetchShares(in: stores)
let share = shares?.first(where: { $0.title == title })
return share
}
func shareTitles() -> [String] {
let stores = [privatePersistentStore, sharedPersistentStore]
let shares = try? persistentContainer.fetchShares(in: stores)
return shares?.map { $0.title } ?? []
}
private func configure<T: CloudKitSharable>(share: CKShare, with item: T) {
share[CKShare.SystemFieldKey.title] = item.sharedTitle
}
}
extension PersistenceController {
func addParticipant(emailAddress: String, permission: CKShare.ParticipantPermission = .readWrite, share: CKShare,
completionHandler: ((_ share: CKShare?, _ error: Error?) -> Void)?) {
/**
Use the email address to look up the participant from the private store. Return if the participant doesn't exist.
Use privatePersistentStore directly because only the owner may add participants to a share.
*/
let lookupInfo = CKUserIdentity.LookupInfo(emailAddress: emailAddress)
let persistentStore = privatePersistentStore //share.persistentStore!
persistentContainer.fetchParticipants(matching: [lookupInfo], into: persistentStore) { (results, error) in
guard let participants = results, let participant = participants.first, error == nil else {
completionHandler?(share, error)
return
}
participant.permission = permission
participant.role = .privateUser
share.addParticipant(participant)
self.persistentContainer.persistUpdatedShare(share, in: persistentStore) { (share, error) in
if let error = error {
print("\(#function): Failed to persist updated share: \(error)")
}
completionHandler?(share, error)
}
}
}
func deleteParticipant(_ participants: [CKShare.Participant], share: CKShare,
completionHandler: ((_ share: CKShare?, _ error: Error?) -> Void)?) {
for participant in participants {
share.removeParticipant(participant)
}
/**
Use privatePersistentStore directly because only the owner may delete participants to a share.
*/
persistentContainer.persistUpdatedShare(share, in: privatePersistentStore) { (share, error) in
if let error = error {
print("\(#function): Failed to persist updated share: \(error)")
}
completionHandler?(share, error)
}
}
}
// Core Data models
extension TripsLog {
#NSManaged var name: String
#NSManaged var identifier: String
#NSManaged var entries: NSSet
}
extension Trip {
#NSManaged var identifier: String
#NSManaged var name: String
#NSManaged var date: Date
#NSManaged var comments: String?
#NSManaged var leaderName: String
#NSManaged var images: NSSet?
}
If anyone is able to shed any light on this I'd really appreciate it, as Apple's own documentation is somewhat lacking. Many thanks!
I have added realm integration in my app. Process is that,
1] if list is not empty then reload the tableview , and at the same time call an api, receive its response..
2] check value present against Id or not, if present delete that value from realm, again add value to realm and reload the tableview.. Code is working fine, if i wait for the 2nd step completion. But 2nd step is totally asynchronous..Here is what I have tried
And crash is happening when i change viewcontroller before the completion 2nd step.
public class Features : Object , Codable{
#objc dynamic var intellinectsId : String?
#objc dynamic var serviceName : String?
#objc dynamic var android_icon : String?
#objc dynamic var parentUrl : String?
#objc dynamic var url : String?
#objc dynamic var mobileOrder : String?
enum CodingKeys: String, CodingKey {
case serviceName = "serviceName"
case android_icon = "android_icon"
case parentUrl = "parentUrl"
case url = "url"
case mobileOrder = "mobileOrder"
case intellinectsId = "intellinectsId"
}
required convenience public init(from decoder: Decoder) throws {
self.init()
let values = try decoder.container(keyedBy: CodingKeys.self)
serviceName = try values.decodeIfPresent(String.self, forKey: .serviceName)
android_icon = try values.decodeIfPresent(String.self, forKey: .android_icon)
parentUrl = try values.decodeIfPresent(String.self, forKey: .parentUrl)
url = try values.decodeIfPresent(String.self, forKey: .url)
mobileOrder = try values.decodeIfPresent(String.self, forKey: .mobileOrder)
intellinectsId = try values.decodeIfPresent(String.self, forKey: .intellinectsId)
}
required init() {
}
}
I have created single class for RealmService
class RealmService {
private init() {}
/// To get singleton class
static let shared = RealmService()
/// To get realm object instance
var realm = try! Realm()
/// To create a record or adding new object to database
func create<T: Object>(_ object: T) {
do {
try realm.safeWrite {
//realm.add(object)
realm.create(T.self,value: object)
//realm.create(T.self, value: object, update: .modified)
}
} catch {
post(error)
}
}
/// To update/modify particular record/object in the database
func update<T: Object>(_ object: T, with dictionary: [String: Any?]) {
do {
try realm.write {
for (key, value) in dictionary {
object.setValue(value, forKey: key)
}
realm.add(object, update: .all) //add(object, update: true)
}
} catch {
post(error)
}
}
/// To delete/remove record or object database
func delete<T: Object>(_ object: T) {
do {
try realm.write {
realm.delete(object)
}
} catch {
post(error)
}
}
/// To handle the errors while performing realmDB opration
func post(_ error: Error) {
NotificationCenter.default.post(name: NSNotification.Name("RealmError"), object: error)
}
/// To observe realm error in the particular View controller
func observeErrors(in vc: UIViewController, completionHandler: #escaping(Error?) -> Void) {
NotificationCenter.default.addObserver(forName: NSNotification.Name("RealmError"), object: nil, queue: nil) { (notification) in
completionHandler(notification.object as? Error)
}
}
/// To stop observing errors in particular View Controller
func stopObservingErrors(in vc: UIViewController) {
NotificationCenter.default.removeObserver(vc, name: NSNotification.Name("RealmError"), object: nil)
}
/// To delete complete realm form the app
func completeDeleteRealm() {
let realmURL = Realm.Configuration.defaultConfiguration.fileURL!
let realmURLs = [
realmURL,
realmURL.appendingPathExtension("lock"),
realmURL.appendingPathExtension("note"),
realmURL.appendingPathExtension("management")
]
for URL in realmURLs {
do {
try FileManager.default.removeItem(at: URL)
} catch {
post(error)
}
}
}
}
Now in View controller , I am taking a value from realm, against id, like this
var dashboardList : Results<Features>{
get{
return RealmService.shared.realm.objects(Features.self).filter(NSPredicate(format: "intellinectsId == %#", HelperFunctions().getUserInfoFromDefaults()?.intellinectsId ?? ""))
}
}
.. as given in step 1st, if dashboardList count is > 0 then reload the tableview , simultaneously , call and api to fetch the details and again I have performed 2nd step like below
if let responseList = response.array, responseList.count > 0{
//remove existing data from realm
let removeHMC = RealmService.shared.realm.objects(Features.self).filter(NSPredicate(format: "intellinectsId == %#",intellinectsId))
if let realm = try? Realm(){
try? realm.write {
realm.delete(removeHMC)
}
}
if let featuresList = responseList[0]["features"].array, featuresList.count > 0{
for val in featuresList{
let feature = Features()
feature.intellinectsId = intellinectsId
feature.serviceName = val["serviceName"].string
feature.android_icon = val["android_icon"].string
feature.parentUrl = val["parentUrl"].string
feature.url = val["url"].string
feature.mobileOrder = val["mobileOrder"].string
RealmService.shared.create(feature)
}
}
}
And if i wait for this completion then it works fine. but if i go on next vc. I am getting an error like
libc++abi.dylib: terminating with uncaught exception of type NSException
after looking at the specific issue, I received an error
Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
I applied this trick also.. But after deleting value from realm , showing again to the tableview causes this issue. I am unable to figure out. Kindly help
In your response when you are deleting the previous features list you are creating a new instance of Realm() which will cause inconsistencies between those two.
Try removing that and using the same RealmService.shared like this
//remove existing data from realm
let removeHMC = RealmService.shared.realm.objects(Features.self).filter(NSPredicate(format: "intellinectsId == %#",intellinectsId))
RealmService.shared.delete(removeHMC)
After updating to XCode 8, Swift 3, and iOS 10, I've begun to get the following error when requesting permission to access Facebook accounts:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Access options are not
permitted for this account type. The options argument must be nil.'
I've found three mentions of this error on SO:
Access options are not permitted for this account type. The options argument must be nil.;
Facebook SDK login crash
NSInvalidArgumentException thrown from ACAccountStore when calling [FBSession openActiveSessionWithPermissions...] on iOS 6.0 and iOS 6.0.1
The first two have no answers; the last suggests checking that the value returned from accountType(withAccountTypeIdentifier ...) isn't nil, which I was already doing.
As mentioned elsewhere, this error is inconsistent with the documentation, which states that Facebook requires an options dictionary.
Here is a small example which reproduces the problem and makes a big show of error checking everything I could think to error check:
import UIKit
import Social
import Accounts
class DVSocialTestViewController: UIViewController {
let localURL = URL(string: "fb://")
let serviceType = SLServiceTypeFacebook
let accountID = ACAccountTypeIdentifierFacebook
var accountType: ACAccountType? {
return ACAccountStore().accountType(withAccountTypeIdentifier: self.accountID)
}
var accounts: [Any]? {
guard let ac = self.accountType else { return nil }
return ACAccountStore().accounts(with: ac)
}
static let appID = "MyAppID"
let basicOptions =
[ACFacebookAppIdKey: DVSocialTestViewController.appID,
ACFacebookPermissionsKey: ["basic_info"],
ACFacebookAudienceKey: ACFacebookAudienceFriends] as [AnyHashable: Any]
let publishOptions =
[ACFacebookAppIdKey:DVSocialTestViewController.appID,
ACFacebookPermissionsKey:["publish_actions"],
ACFacebookAudienceKey:ACFacebookAudienceFriends] as [AnyHashable: Any]
var applicationIsInstalled: Bool {
guard let url = self.localURL else {
return false
}
return UIApplication.shared.canOpenURL(url)
}
var serviceIsAvailable: Bool {
return SLComposeViewController.isAvailable(forServiceType: self.serviceType)
}
var userIsLoggedIn: Bool {
guard let ac = self.accounts, ac.count > 0 else {
NSLog("\(self.accounts)")
return false
}
return true
}
func requestPermissionsForAccountType(_ type: ACAccountType) {
ACAccountStore().requestAccessToAccounts(with: type, options: self.basicOptions) {
(success: Bool, error: Error?) -> Void in
if !success {
NSLog("\(error)")
return
}
ACAccountStore().requestAccessToAccounts(with: type, options: self.publishOptions) {
(success: Bool, error: Error?) -> Void in
if !success {
NSLog("\(error)")
return
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
guard self.applicationIsInstalled else {
NSLog("FB Application is not installed.")
return
}
guard self.serviceIsAvailable else {
NSLog("FB Service is unavailable.")
return
}
guard self.userIsLoggedIn else {
NSLog("User is not logged into FB.")
return
}
guard let AT = self.accountType else {
NSLog("No accounts.")
return
}
self.requestPermissionsForAccountType(AT)
}
}
Has anyone run into this? Any suggestions about what I'm missing?
Edit:
There are few reasons why I feel my question is different from the proposed duplicate.
I'm not using the Facebook SDK.
This is not a "rare case"--it consistently happens with this example every time I run it.
This specifically began happening upon updating to XCode 8, Swift 3, and iOS 10 from the preceding versions.
As stated above, the only answer to the other question (which I admit is not accepted) does not solve my problem.
I've provided a complete, minimal example, which the other question does not.
I am trying to do Error handling using swift 2.1,
The following scenario,
var test: NSArray! = ["Test1", "Test2"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
do{
try testing()
} catch {
print("error")
}
}
func testing() throws {
print(test.objectAtIndex(7))
}
At the above case, My application crashes & saying terminating with uncaught exception of type NSException but I am expecting the control is supposed to be inside the Catch block instead of crash.
May I know the solution for this. Can anybody please do the needful on this
The only way you can make that work is to throw an error (as Eric D. pointed out in the comments):
Playground:
enum ArrayError : ErrorType{
case OutOfBounds
}
class SomeClass {
var test: NSArray! = ["Test1", "Test2"]
func testCode() {
do{
try testing(3)
} catch let error{
print("error = \(error)") // for index 3 this would print "error = OutOfBounds\n"
}
}
func testing(index: Int) throws -> String {
guard index < test.count else{
throw ArrayError.OutOfBounds
}
return test[index] as! String
}
}
let sC = SomeClass()
sC.testCode()
What I want to do is store data using core data.
I've got code like below with fetching, my problem is that sometimes when I run application wpis2.count gives 0 no idea why (i've got information: "data not loaded" without any error). Is there any way that I should prepare core data before use?
Ustawienia are:
extension Ustawienia {
#NSManaged var ust: String?
#NSManaged var wym: String?
}
PS. Data exists for sure, because when I run application again it works OK. Any idea how solve it?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let entity2 = NSFetchRequest(entityName: "Ustawienia")
entity2.fetchLimit = 1
do{
let wpis2 = try moc3.executeFetchRequest(entity2) as! [NSManagedObject]
let ust = wpis2 as [NSManagedObject]
if (wpis2.count==1){
let ustawienia = wpis2[0] as! Ustawienia
if (ustawienia.ust=="n"){
tabela="n"
}
else
{
tabela="p"
}
if (ustawienia.wym=="w"){
wymienniki="w"
}
else
{
wymienniki="p"
}
}
else
{
NSLog("data not loaded")
}
}
catch
{
fatalError("error \(error)")
}
}