Swift Mapkit remove annotations and release memory - swift

I am using mapView.removeAnnotations(mapView.annotations) to remove annotations but I can see from debug in Xcode that the memory for each annotation is not released and this eventually causes my app to crash. Is there a way to release it? I have looked at ARC and deinit and weak but I can't see how this relates to my code.
import Foundation
import MapKit
class StationMarkerView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
willSet {
guard let station = newValue as? Station else {
return
}
canShowCallout = true
let mapsButton = FavouriteButton()
let bool = station.is_favourite
let image = bool! ? "star.fill": "star"
mapsButton.setBackgroundImage(UIImage(systemName: image), for: .normal)
rightCalloutAccessoryView = mapsButton
markerTintColor = station.markerTintColor
glyphImage = station.glyphImage
}
}
}
I have tried using weak for "mapButton" but Xcode gives me a dealocation warning.
Thanks any help appreciated.

I found a quick and dirty solution to my problem. Removing the annotations and then changing the maptype seems to release all memory relating to the mapView.
So to refresh my map I do ...
mapView.removeAnnotations(mapView.annotations)
mapView.mapType = MKMapType.satellite
velibManager.fetchVelib()
mapView.mapType = MKMapType.standard

Related

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:.

How to change non selected annotation pin images on mapkit with Swift

I have a map and on this map, I have 10 custom annotation pins. All pins have the same custom image. When I click on a pin, I need to change all the other 9 annotation's images. it's possible to change the clicked pin's image but I need to keep as it is and I need to change all other pins images.
I tried to get all annotations with Map mapView.annotations and tried to find selected annotations and change other images but couldn't manage it. And idea how to do it?
Thanks in advance.
Conform to MKMapViewDelegate protocol and then:
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
let selectedAnnotation = view.annotation
for annotation in mapView.annotations {
if let annotation = annotation as? MKAnnotation where !annotation.isEqual(selectedAnnotation) {
// do some actions on non-selected annotations in 'annotation' var
}
}
Also you can save the selected annotation for later use here, if you want to process all annotations in another moment.
finally managed :) solved the problem little bit hard way but working smooth :) thank you for the tip rshev ;)
i used a bool for tap recognize
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is CustomAnnotation {
var pin = mapView.dequeueReusableAnnotationViewWithIdentifier(customAnnotationViewIdentifier)
pin = MKPinAnnotationView(annotation: annotation, reuseIdentifier: customAnnotationViewIdentifier)
if tapControl {
pin.image = UIImage(named: "MapAnnotationIcon")
} else {
pin.image = UIImage(named: "SelectedMapAnnotationIcon")
}
if pin == nil {
pin.canShowCallout = false
} else {
pin.annotation = annotation
}
return pin
and when pin tapped ->
if let annotation = view.annotation as? CustomAnnotation {
tapControl = !tapControl
for annotation in mapView.annotations {
if let annotation = annotation as? MKAnnotation where !annotation.isEqual(selectedAnnotation) {
mapView.removeAnnotation(annotation)
}
}
addAnnotations()
println("tapped")
i removed all pins without selected pin, and then draw them back but this time tapcontrol is false so other pins are redrawed with another imageview, so thats what i exactly want to do.
You just have to overrride isSelected property inside your MKAnnotationView subclass.
override var isSelected: Bool {
didSet {
if isSelected {
// do stuff where annotation is selected
} else {
// do opposite
}
}
}

How to fix Objective C Methods SWIFT

How would I fix this problem?
Here's the code
//
// TTTImageView.swift
// TicTacToe
import UIKit
class TTTImageView: UIImageView {
var player:String?
var activated:Bool! = false
Problem is here and states "Method 'setPlayer' with objective c selector 'setPlayer:'conflicts with setter for 'player'with same objective c selector"
func setPlayer (_player:String){
self.player = _player
if activated == false{
if _player == "x"{
self.image = UIImage(named: "x")
}else{
self.image = UIImage(named: "o")
}
activated = true
}
}
}
I haven't tried anything yet but will greatly appreciate any help.
There error is explained here:
https://stackoverflow.com/a/28500768/3149796
There are lots of great Swift features that you can use instead of what you've got. Try a property observer (didSet) instead of creating a custom setter function. You can also clean up your code and make it safer with an optional binding (if let) and a where clause.
Also your activated property shouldn't be implicitly unwrapped (Bool!), but rather just Bool.
import UIKit
class TTTImageView: UIImageView {
var activated: Bool = false
var player: String? {
didSet {
if let player = self.player where self.activated == false {
if self.player == "x" {
self.image = UIImage(named: "x")
} else {
self.image = UIImage(named: "o")
}
self.activated = true
}
}
}
}
I had the same problem and I was able to fix it by using #nonobjc
More information here:
Swift 2, method 'setOn' with Objective-C selector 'setOn:' conflicts with setter for 'on' with the same Objective-C selector
change setPlayer to Player
I hope this helps

Find out if statUpdatingLocation is active in Swift

I have a location based app running in swift. I am trying to detect if the self.locationManager.startUpdatingLocation() is currently active.
I am struggling to find out how to do this nor can I find much on the internet about it. This I am fairly sure is rather simple to achieve. I don't want to set a BOOL as this needs to be global.
if CLLocationManager.locationServicesEnabled() && /* START UPDATE LOCATION GOES HERE */ {
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestAlwaysAuthorization()
self.locationManager.startUpdatingLocation()
//self.locationManager.startMonitoringSignificantLocationChanges()
sender.setTitle("END DAY", forState: UIControlState.Normal)
} else {
}
I know this is a late answer now. But this is possibly one solution if you want to know if the locationManager is updating.
There should only be one instance of CLLocationManager in your app. So creating a Singleton is ideal. Then, you should override the methods startUpdatingLocation and stopUpdatingLocation.
(Swift 3)
import CoreLocation
class LocationManager: CLLocationManager {
var isUpdatingLocation = false
static let shared = LocationManager()
override func startUpdatingLocation() {
super.startUpdatingLocation()
isUpdatingLocation = true
}
override func stopUpdatingLocation() {
super.stopUpdatingLocation()
isUpdatingLocation = false
}
}
Usage:
if LocationManager.shared.isUpdatingLocation {
print("Is currently updating location.")
} else {
print("Location updates have stopped.")
}
You cannot not know whether startUpdatingLocation() is "active" because you are the one who said it.
If you need to keep track of this from elsewhere, make a Bool property and set it to true when you call startUpdatingLocation() and to false when you call stopUpdatingLocation().

UIImageView is NIL

I have a default image in viewItem to make sure that it is working, it shows on the detail view of the splitview.
#IBOutlet weak var ImageView: UIImageView!
var imageCache = [String: UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
self.configureView()
}
func configureView() {
if let detail: AnyObject = self.detailItem {
if let label = self.detailDescriptionLabel {
let dict = detail as [String: String]
label.text = ""
let s = dict["result"]
let vr = NString(string: s!)
let vrd = vr.doubleValue
let value = ceil(vrd*20)
let valueString = String(format: "%.0f", value)
vresult.text = "\(valueString)%"
getPic(dict) // <---- trouble maker
fitem.hidden = false
ritem.hidden = false
}
} else {
navigationController?.popViewControllerAnimated(true)
}
}
func getPic(item: [String: String]) {
var chachedImage = self.imageCache[item["image"]!]
println(item["image"]) // <-- prints out the url
if cachedImage == nil {
var imgUrl = NSURL(string: item["image"]!)
let request: NSURLRequest = NSURLRequest(URL: imgUrl!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {( reponse: NSURLResponse!, data: NSData!, error; NSError!) -> Void in
if error == nil {
cachedImage = UIImage(data: data)
println("got here no problem") // <-- prints out
self.imageCache[item["image"]!] = cachedImage
println(self.imageCache) // <-- prints reference OK
dispatch_async(dispatch_get_main_queue(), {
self.ImageView.image = cachedImage // <---- offender
})
} else {
println("Error: \(error.localizedDescription)")
}
})
} else {
dispatch_async(dispatch_get_main_queue(), {
self.ImageView.image = cachedImage
})
}
}
ImageView is coming up nil every time.
fatal error: unexpectedly found nil while unwrapping an Optional value
but the default image shows. I've moved this out of the dispatch and even tried setting it straight from the viewDidLoad() always errors. It used to be a UIWebView and worked perfectly except that it would not cache anything. Since loading these images is a lot of work, I thought caching would be good, I've got caching working for thumbnails in the MASTER view.
It may be because of how your instaciating your viewcontroller.
let vc = MyViewController()
Something like this wont work. You're creating the VC without actually giving the storyboard a chance to link the IBOutlets. Instead use
storyboard.instantiateViewControllerWithIdentifier(identifier: String)
You may need to get reference to the storyboard using
let storyboard = UIStoryboard(name: name, bundle: NSBundle.mainBundle())
Hope this helps :)
Changing your variable name shouldn't make any difference except for readibility/maintainability unless there's a namespace conflict (good to understand why/where that might be happening). Also I was wondering - you made the IBOutlet'ed varable weak. When the last remaining strong ref to the object goes away, the weak references to the object are set nil by the runtime/garbage collector automatically. (Look up that section of the Swift documentation if you're not solid about it).
Maybe you should check your classes and controllers by adding deinit { println(,"function name deallocated' }. Between your use of weak and improved behavior seen when you change the variable name, it seems like there might be some weird (buggy) interactions going on in your app itself.
Well silly me. I've been working on this for a few days, I got the great idea to try and change the name, and it worked. I tried changing it back and it broke, apparently you can't use ImageView as a variable!
In my case was because I was using a nib and didn't register it.
Once I did registered it, it worked
My case Was Different I used
awakeFromNib()
instead of
viewDidLoad()
.