mapView:didAddAnnotationViews doesn't always pick up the annotations - swift

I have placed a number of annotations on a map, and when the user touches the location button, the view zooms in on the user location, which contains a number of annotations.
mapView:didAddAnnotationViews is called and should add all the annotations inside the current view into an NSArray.
Sometimes it adds all the annotations without any problems, but other times, it only adds the one annotation which is the user location (blue dot).
I suspect it may have to do with being called before the map has finished rendering, but i'm not sure.
Not sure if it's possible to link the function mapViewDidFinishRenderingMap to didAddAnnotationViews so that it will only load once the view has been completed loading (including all the annotations).
Would appreciate any advice regarding this, as I am relatively new to Swift.
Here's some of my code. I've got a timer that runs every second and prints out the size of the array in the console.
var timer = NSTimer()
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "update", userInfo: nil, repeats: true)
func mapView (mapView: MKMapView!, didAddAnnotationViews views: [AnyObject]!) {
pinannotationarray = views
}
func update() {
if (pinannotationarray != nil && locationManager.location != nil)
{
println("pinannotationarray size \(pinannotationarray.count)")
}
}
** Revised Code **
var annSet: AnyObject!
func mapView(mapView: MKMapView!, regionDidChangeAnimated animated: Bool) {
annSet = mapView.annotationsInMapRect(mapView.visibleMapRect)
println("regionDidChangeAnimate: annSet count = \(annSet.count)")
for (var i = 0; i < annSet.count; i++)
{
pinannotation = annSet.allObjects[i].annotation as? MyAnnotation
//fatal error: unexpectedly found nil while unwrapping an Optional value
println(pinannotation.title)
}
}

The annotationsInMapRect method returns an NSSet containing the annotation model objects (eg. MKPointAnnotation) -- not the views (eg. MKAnnotationView).
That also means annSet.allObjects will be an NSArray containing the annotation model objects.
That means annSet.allObjects[i].annotation is invalid and will fail (annotation views have an annotation property and not the annotations themselves) which leaves pinannotation as nil resulting in the crash.
Change the assignment to:
pinannotation = annSet.allObjects[i] as? MyAnnotation
println(pinannotation.title)
or to be a little safer:
if let pa = annSet.allObjects[i] as? MyAnnotation {
println(pa.title)
}
By the way, you don't even need to generate an NSArray from the NSSet. You can iterate the NSSet directly:
for ann in annSet as NSSet {
if let ma = ann as? MyAnnotation {
println("ma.title = \(ma.title)")
}
}

Related

ScrollView IBOutlet nil when using protocol to call function in viewDidDisappear

I have a scrollView that contains a dynamic amount of WeatherViewControllers each displaying the weather data of a different city the user has saved. The user can segue from the WeatherViewControllers to a CityListViewController. Where they can add and remove cities from their list which in turn should add and remove WeatherViewControllers from the scrollView upon dismissing the CityListViewController, this is where I am running into a problem.
Currently I am trying to use a protocol in to call the func reloadScrollView which calls viewDidLoad in the scrollViewController upon dismissing(viewDidDisappear) the CityListViewController but am getting an error:
Fatal error: Unexpectedly found nil while unwrapping an Optional value: file
when it gets to:
totalScrollView.addSubview(weatherScreen.view)
Using debugger I have found that totalScrollView is nil and that is causing the problem. Is there a way to make the scrollView load so it is not nil when dismissing the other viewController
OR
is the a better time to call use this protocol to call this function?
Side Note: Upon initially opening the app the scrollView loads properly with all the correct WeatherViewControllers in the UIScrollView and the correct cities in the list.
class ScrollViewController: UIViewController, ScrollReloadProtocol {
func reloadScrollView() {
print("SCROLL RELOADED!!!!!*******")
self.viewDidLoad()
}
#IBOutlet var totalScrollView: UIScrollView!
var pages = [ViewController]()
var x = 0
var weatherScreensArray = [SavedCityEntity]()
var weatherScreenStringArray = [String]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var horizString = "H:|[page1(==view)]"
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
//userDefaults used to keep track of which screen is which to put different cities on different viewControllers
defaults.set(0, forKey: "screenNumber")
//load cities to get number of cities saved
loadCities()
var views : [String: UIView] = ["view": view]
//create all weatherWeatherControllers
while x <= weatherScreensArray.count {
pages.append(createAndAddWeatherScreen(number: x))
weatherScreenStringArray.append("page\(x+1)")
views["\(weatherScreenStringArray[x])"] = pages[x].view
let addToHoriz = "[\(weatherScreenStringArray[x])(==view)]"
horizString.append(addToHoriz)
x+=1
}
horizString.append("|")
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[page1(==view)]|", options: [], metrics: nil, views: views)
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: horizString, options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views)
NSLayoutConstraint.activate(verticalConstraints + horizontalConstraints)
}
//Function to create and add weatherViewController
func createAndAddWeatherScreen(number: Int) -> ViewController {
defaults.set(number, forKey: "screenNumber")
let story = UIStoryboard(name: "Main", bundle: nil)
let weatherScreen = story.instantiateViewController(identifier: "View Controller") as! ViewController
weatherScreen.view.translatesAutoresizingMaskIntoConstraints = false
totalScrollView.addSubview(weatherScreen.view)
addChild(weatherScreen)
weatherScreen.didMove(toParent: self)
return weatherScreen
}
}
Skipping that fact that your are not doing it right, let's forcus on the one issue at a time. You are trying to access the totalScrollView implicitly in the viewDidLoad where if the outlet is linked it should be loaded at that point. If it is nil you should:
Make sure that you have the .storyboard or .xib file defining the ScrollViewController layout.
Make sure you are loading this controller from that storyboard/xib.
Make sure that the view controller in the storyboard/xib file has set its class to ScrollViewController, similar to the following print screen:
Make sure that the outlet is linked in the storyboard/xib to this property in your code file (probably ScrollViewController.swift). If not:
open storyboard and sorucecode file in separate editors
drag and drop from the dot on the left of the property declaration to the UIScrollView in the storyboard
make sure that there is added a link to Referencing Outlets

Unable to update NSTouchBar programmatically

I am currently developing a very simple Live Scores MAC OSX app for personal use where I show a bunch of labels (scores) on the touch bar. What I am trying to achieve in a few steps:
Fetch live soccer scores from a 3rd party API every 30 seconds
Parse the scores and make them into labels
Update the touch bar with new scores
[Please note here that this app will not be published anywhere, and is only for personal use. I am aware of the fact that Apple strictly advises against such type of content in the Touch Bar.]
Here is the code that I wrote following basic Touch Bar tutorial from RW (https://www.raywenderlich.com/883-how-to-use-nstouchbar-on-macos). Skeleton of my code is picked from the RW tutorial:
In WindowController (StoryBoard entry point), override makeTouchBar like this:
override func makeTouchBar() -> NSTouchBar? {
guard let viewController = contentViewController as? ViewController else {
return nil
}
return viewController.makeTouchBar()
}
In ViewController, which is also the Touch Bar Delegate, implement the makeTouchBar fn:
override func makeTouchBar() -> NSTouchBar? {
let touchBar = NSTouchBar()
touchBar.delegate = self
touchBar.customizationIdentifier = .scoresBar
touchBar.defaultItemIdentifiers = [.match1, .flexibleSpace, .match2, ... , .match10]
return touchBar
}
NSTouchBarDelegate in ViewController. scores is where I store my fetched scores (See 5). I return nil for views if scores aren't fetched yet:
extension ViewController: NSTouchBarDelegate {
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
if (<scores not fetched yet>) {
return nil
}
// matchNum is the match number for which I am showing scores for
let customViewItem = NSCustomTouchBarItem(identifier: identifier)
customViewItem.view = NSTextField(labelWithString: self.scores[matchNum ?? 0])
return customViewItem
}
}
To fetch scores periodically I am running a scheduled task Timer in viewDidLoad() of my viewcontroller like this:
_ = Timer.scheduledTimer(timeInterval: 30.0, target: self, selector: #selector(ViewController.fetchScores), userInfo: nil, repeats: true)
And finally, this is my fetchScores function that also makes a call to update the Touch Bar:
#objc func fetchScores() {
let url = "<scores api end point>"
Alamofire.request(url).responseJSON { response in
if let json = response.result.value {
// update self.scores here and fill it with latest scores
if #available(OSX 10.12.2, *) {
//self.touchBar = nil
self.touchBar = self.makeTouchBar() // This is where I am calling makeTouchBar again to update Touch Bar content dynamically
}
}
}
My understanding from the code above is that once I make a call to makeTouchBar in fetchScores and assign it to my viewcontroller's touchBar property, it should ideally call touchBar(:makeItemForIdentifier) delegate function to update the Touch Bar view (SO thread on this). But in my case, touchBar(:makeItemForIdentifier) is never called. The only time touchBar(:makeItemForIdentifier) is called is the first time, when makeTouchBar is called from my WindowController (See point 1 above). And since scores have not been retrieved yet, my touch bar remains empty.

How can I set NSFetchedResultsController's section sectionNameKeyPath to be the first letter of a attribute not just the attribute in Swift, NOT ObjC

I'm rewriting an old obj-c project in swift that has a tableView with a sectionIndex
I've set a predicate so it only returns objects with the same country attribute
I want to make the section index based on the first letter of the country attribute
in obj-c i created the fetchedResultsController like this
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"name.stringGroupByFirstInitial" cacheName:nil];
and i had an extension on NSString
#implementation NSString (Indexing)
- (NSString *)stringGroupByFirstInitial {
if (!self.length || self.length == 1)
return self;
return [self substringToIndex:1];
}
this worked fine in conjunction with the two methods
- (NSArray *) sectionIndexTitlesForTableView: (UITableView *) tableView{
return [self.fetchedResultsController sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index{
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
In swift I tried to create a similar extension, with snappier name :)
extension String {
func firstCharacter()-> String {
let startIndex = self.startIndex
let first = self[...startIndex]
return String(first)
}
}
which works fine in a playground returning the string of the first character of any string you call it on.
but using a similar approach creating the fetchedResultsController, in swift, ...
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: (dataModel?.container.viewContext)!,
sectionNameKeyPath: "country.firstCharacter()",
cacheName: nil)
...causes an exception. heres the console log
2018-02-23 11:41:20.762650-0800 Splash[5287:459654] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key firstCharacter().'
Any suggestions as the correct way to approach this would be appreciated
This is not a duplicate of the question suggested as this is specifically related to how to achieve this in Swift. I have added the simple correct way to achieve this in Obj -c to the other question
Fastest solution (works with for Swift 4)
The solution from pbasdf works. It's much faster than using a transient property.
I have a list of about 700 names to show in a tableview, with a relative complex fetch from CoreData.
Just doing the initial search:
With a transient property returning a capitalised first letter of 'name': Takes 2-3s to load. With some peaks of up to 4s !
With the solution from pbasdf ( #obj func nameFirstCharacter() -> String, this loading time goes down to 0.4 - 0.5s.
Still the fastest solution is adding a regular property 'nameFirstChar' to the model. And make sure it is indexed. In this case the loading time goes down to 0.01 - 0.02s for the same fetch query!
I have to agree that I agree with some remarks that it looks a bit dirty to add a property to a model, just for creating sections in a tableview. But looking at the performance gain I'll stick to that option!
For the fastest solution, this is the adapted implementation in Person+CoreDataProperties.swift so updating the namefirstchar is automatic when changes to the name property are made. Note that code autogeneration is turned off here. :
//#NSManaged public var name: String? //removed default
public var name: String? //rewritten to set namefirstchar correctly
{
set (new_name){
self.willChangeValue(forKey: "name")
self.setPrimitiveValue(new_name, forKey: "name")
self.didChangeValue(forKey: "name")
let namefirstchar_s:String = new_name!.substring(to: 1).capitalized
if self.namefirstchar ?? "" != namefirstchar_s {
self.namefirstchar = namefirstchar_s
}
}
get {
self.willAccessValue(forKey: "name")
let text = self.primitiveValue(forKey: "name") as? String
self.didAccessValue(forKey: "name")
return text
}
}
#NSManaged public var namefirstchar: String?
In case you don't like or can't add a new property or if you prefer to keep the code autogeneration on, this is my tweaked solution from pbasdf:
1) Extend NSString
extension NSString{
#objc func firstUpperCaseChar() -> String{ // #objc is needed to avoid crash
if self.length == 0 {
return ""
}
return self.substring(to: 1).capitalized
}
}
2) Do the sorting, no need to use sorting key name.firstUpperCaseChar
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
let personNameSort = NSSortDescriptor(key: "name", ascending: true, selector: #selector(NSString.caseInsensitiveCompare))
let personFirstNameSort = NSSortDescriptor(key: "firstname", ascending: true, selector: #selector(NSString.caseInsensitiveCompare))
fetchRequest.sortDescriptors = [personNameSort, personFirstNameSort]
3) Then do the fetch with the right sectionNameKeyPath
fetchRequest.predicate = ... whatever you need to filter on ...
fetchRequest.fetchBatchSize = 50 //or 20 to speed things up
let fetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: managedObjectContext,
sectionNameKeyPath: "name.firstUpperCaseChar", //note no ()
cacheName: nil)
fetchedResultsController.delegate = self
let start = DispatchTime.now() // Start time
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to initialize FetchedResultsController: \(error)")
}
let end = DispatchTime.now() // End time
let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds // time difference in nanosecs
let timeInterval = Double(nanoTime) / 1_000_000_000
print("Fetchtime: \(timeInterval) seconds")
Add a function to your DiveSite class to return the first letter of the country:
#objc func countryFirstCharacter() -> String {
let startIndex = country.startIndex
let first = country[...startIndex]
return String(first)
}
Then use that function name (without the ()) as the sectionNameKeyPath:
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: (dataModel?.container.viewContext)!,
sectionNameKeyPath: "countryFirstCharacter",
cacheName: nil)
Note that the #objc is necessary here in order to make the (Swift) function visible to the (Objective-C) FRC. (Sadly you can't just add #objc to your extension of String.)

Does this view controller leak in a "willSet/didSet" pair?

You have a vc (green) and it has a panel (yellow) "holder"
Say you have ten different view controllers...Prices, Sales, Stock, Trucks, Drivers, Palettes, which you are going to put in the yellow area, one at a time. It will dynamically load each VC from storyboard
instantiateViewController(withIdentifier: "PricesID") as! Prices
We will hold the current VC one in current. Here's code that will allow you to "swap between" them...
>>NOTE, THIS IS WRONG. DON'T USE THIS CODE<<
One has to do what Sulthan explains below.
var current: UIViewController? = nil {
willSet {
// recall that the property name ("current") means the "old" one in willSet
if (current != nil) {
current!.willMove(toParentViewController: nil)
current!.view.removeFromSuperview()
current!.removeFromParentViewController()
// "!! point X !!"
}
}
didSet {
// recall that the property name ("current") means the "new" one in didSet
if (current != nil) {
current!.willMove(toParentViewController: self)
holder.addSubview(current!.view)
current!.view.bindEdgesToSuperview()
current!.didMove(toParentViewController: self)
}
}
}
>>>>>>>>IMPORTANT!<<<<<<<<<
Also note, if you do something like this, it is ESSENTIAL to get rid of the yellow view controller when the green page is done. Otherwise current will retain it and the green page will never be released:
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
current = nil
super.dismiss(animated: flag, completion: completion)
}
Continuing, you'd use the current property like this:
func showPrices() {
current = s.instantiateViewController(withIdentifier: "PricesID") as! Prices
}
func showSales() {
current = s.instantiateViewController(withIdentifier: "SalesID") as! Sales
}
But consider this, notice "point X". Normally there you'd be sure to set the view controller you are getting rid of to nil.
blah this, blah that
blah.removeFromParentViewController()
blah = nil
However I (don't think) you can really set current to nil inside the "willSet" code block. And I appreciate it's just about to be set to something (in didSet). But it seems a bit strange. What's missing? Can you even do this sort of thing in a computed property?
Final usable version.....
Using Sulthan's approach, this then works perfectly after considerable testing.
So calling like this
// change yellow area to "Prices"
current = s.instantiateViewController(withIdentifier: "PricesID") as! Prices
// change yellow area to "Stock"
current = s.instantiateViewController(withIdentifier: "StickID") as! Stock
this works well...
var current: UIViewController? = nil { // ESSENTIAL to nil on dismiss
didSet {
guard current != oldValue else { return }
oldValue?.willMove(toParentViewController: nil)
if (current != nil) {
addChildViewController(current!)
holder.addSubview(current!.view)
current!.view.bindEdgesToSuperview()
}
oldValue?.view.removeFromSuperview()
oldValue?.removeFromParentViewController()
if (current != nil) {
current!.didMove(toParentViewController: self)
}
}
// courtesy http://stackoverflow.com/a/41900263/294884
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// ESSENTIAL to nil on dismiss
current = nil
super.dismiss(animated: flag, completion: completion)
}
Let's divide the question into two: (1) Is there a "leak"? and (2) Is this a good idea?
First the "leak". Short answer: no. Even if you don't set current to nil, the view controller it holds obviously doesn't "leak"; when the containing view controller goes out of existence, so does the view controller pointed to by current.
The current view controller does, however, live longer than it needs to. For that reason, this seems a silly thing to do. There is no need for a strong reference current to the child view controller, because it is, after all, your childViewControllers[0] (if you do the child view controller "dance" correctly). You are thus merely duplicating, with your property, what the childViewControllers property already does.
So that brings us to the second question: is what you are doing a good idea? No. I see where you're coming from — you'd like to encapsulate the "dance" for child view controllers. But you are doing the dance incorrectly in any case; you're thus subverting the view controller hierarchy. To encapsulate the "dance", I would say you are much better off doing the dance correctly and supplying functions that perform it, along with a computed read-only property that refers to childViewController[0] if it exists.
Here, I assume we will only ever have one child view controller at a time; I think this does much better the thing you are trying to do:
var current : UIViewController? {
if self.childViewControllers.count > 0 {
return self.childViewControllers[0]
}
return nil
}
func removeChild() {
if let vc = self.current {
vc.willMove(toParentViewController: nil)
vc.view.removeFromSuperview()
vc.removeFromParentViewController()
}
}
func createChild(_ vc:UIViewController) {
self.removeChild() // There Can Be Only One
self.addChildViewController(vc) // *
// ... get vc's view into the interface ...
vc.didMove(toParentViewController: self)
}
I don't think that using didSet is actually wrong. However, the biggest problem is that you are trying to split the code between willSet and didSet because that's not needed at all. You can always use oldValue in didSet:
var current: UIViewController? = nil {
didSet {
guard current != oldValue else {
return
}
oldValue?.willMove(toParentViewController: nil)
if let current = current {
self.addChildViewController(current)
}
//... add current.view to the view hierarchy here...
oldValue?.view.removeFromSuperview()
oldValue?.removeFromParentViewController()
current?.didMove(toParentViewController: self)
}
}
By the way, the order in which the functions are called is important. Therefore I don't advise to split the functionality into remove and add. Otherwise the order of viewDidDisappear and viewDidAppear for both controllers can be surprising.

CoreData threading issue with Google Place Picker with unrelated Managed Object Context

Sorry for the long post; however, I wanted to be through.
I have multiple Managed Object Contexts (moc) and a substantial code working stable and consistently. Except for the current issue. Weird thing is the address of the moc is none of the ones I created - an unrelated moc is crashing that I am not even accessing. Please read on if you have time.
Very simply put, I crate a new child moc and pass it on to the new view controller. User sets some values and saves them. Child moc is saved, then parent. However, in the edit view controller, if the user selects to show the Google Place Picker, just as starting to show the window I get a multithreading error. I get this error on iPhone 6 Plus actual device, but not on iPhone 6S. It works with no problems whatsoever. With 6 Plus, always crashes at exactly the same place.
I've enabled com.apple.CoreData.ConcurrencyDebug 1
This is the main wiew that creates the child view for editing:
// =========================================================================
// EventsTableViewController
class EventsTableViewController: UITableViewController {
// =========================================================================
// MARK: - Target Action Methods
#IBAction func didTapNewEvent(sender: UIBarButtonItem) {
guard let vc = storyboard?.instantiateViewControllerWithIdentifier(EditEventTableViewController.identifier) as? EditEventTableViewController else { DLog("ERROR creating EditEventTableViewController"); return }
// create a child context
let eventContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
eventContext.parentContext = context
eventContext.name = "child_of_mainContext_for_Event"
let event = NSEntityDescription.insertNewObjectForEntityForName(Event.entityName, inManagedObjectContext: eventContext)
vc.segueObject = event
vc.context = eventContext
vc.shouldDismiss = true
vc.delegate = self
let nc = UINavigationController(rootViewController: vc)
presentViewController(nc, animated: true, completion: nil)
}
}
This is the editing view controller where the properties are declared:
// =========================================================================
// EditEventTableViewController
class EditEventTableViewController: UITableViewController {
// =========================================================================
// MARK: - Google Place properties
private var context: NSManagedObjectContext!
private var placePicker: GMSPlacePicker!
}
This is where I call the show map function:
// =========================================================================
// MARK: - UITableViewDelegate
extension EditEventTableViewController {
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath == Cell.Location.indexPath {
didTapSelectFromMap()
}
}
}
This is the google place picker where the code crashes exactly the same spot every time and I am not even accessing any contexts here (I was actually, but deleting them did not solve the problem):
// =========================================================================
// MARK: - Google Places
extension EditEventTableViewController {
func didTapSelectFromMap() {
guard let event = selectedObject as? Event else { DLog("ERROR object is not Event"); return }
guard let location = locationManager.location else {
DLog("Cannot get location, will try again")
locationManager.startUpdatingLocation()
return
}
DLog("current location: \(location)")
// create viewport around where the user is
let center = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let northEast = CLLocationCoordinate2DMake(center.latitude + 0.001, center.longitude + 0.001)
let southWest = CLLocationCoordinate2DMake(center.latitude - 0.001, center.longitude - 0.001)
let viewport = GMSCoordinateBounds(coordinate: northEast, coordinate: southWest)
let config = GMSPlacePickerConfig(viewport: viewport)
placePicker = GMSPlacePicker(config: config)
placePicker.pickPlaceWithCallback { // the code crashes here! But I have no idea where the violation occurs.
(place, error) in
}
}
}
I write everything to log, and the relevant info from the log are below. As you can see, I create 4 contexts, the addresses are shown, but the error is on some MOC that I have not created. Could it be that, GooglePlacePicker is using a moc of its own, and somehow getting mixed with mine :)???
[00059]:CDStack.swift :parentContext................. :12:41:18 CREATED <NSManagedObjectContext: 0x1741dbe40>: parent
[00068]:CDStack.swift :context....................... :12:41:18 CREATED <NSManagedObjectContext: 0x1741dbd50>: main
[00077]:CDStack.swift :importContext................. :12:41:18 CREATED <NSManagedObjectContext: 0x1701dd3d0>: import
[00095]:CDStack.swift :firebaseContext............... :12:41:21 CREATED <NSManagedObjectContext: 0x1741dc020>: firebase
[00127]:EditEventTableViewController.s :viewDidLoad()................. :12:43:48 Context: <NSManagedObjectContext: 0x1741de3c0>: child_of_mainContext_for_Event
[00375]:EditEventTableViewController.s :didTapSelectFromMap()......... :12:43:54 current location: <+78.675603,-93.352320> +/- 1414.00m (speed -1.00 mps / course -1.00) # 25/09/2016, 12:43:54 Southern European Spring Time
2016-09-25 12:43:54.630499 App[1504:413029] [error] error: The current thread is not the recognized owner of this NSManagedObjectContext(0x1703c3de0). Illegal access during objectRegisteredForID:
I am using Xcode Version 8.1 beta (8T29o), Swift 2.3 but saw the same behavior with Xcode Version 7.3.1.
Any pointers are greatly appreciated.
The stack at break is shown below. It does not show which line the violation occurs. If it did, it would have been a lot easier to track down the bug.
EDIT 1 - Save function
class func save(moc:NSManagedObjectContext) {
moc.performBlockAndWait {
if moc.hasChanges {
do {
try moc.save()
} catch {
DLog("ERROR saving context '\(moc)' - \(error)")
}
} else {
// there are no changes
}
if let parentContext = moc.parentContext {
save(parentContext)
}
}
}
You are calling -[NSManagedObjectContext save:] in the -[GMSTileDataCache storeCacheableTileDatas:completionHandler:] completion block. That is being executed on a background thread and I suspect you are calling save on a context that is not associated with that background thread.
Quick answer is to wrap that save in a -performBlockAndWait:.