mapbox / mapbox-gl-native

Interactive, thoroughly customizable maps in native Android, iOS, macOS, Node.js, and Qt applications, powered by vector tiles and OpenGL
https://mapbox.com/mobile
Other
4.37k stars 1.32k forks source link

Custom MGLAnnotationView Does Not Behave Properly #8873

Closed cansurmeli closed 7 years ago

cansurmeli commented 7 years ago

Hello.

I'm trying to implement a custom annotation view where an annotation will have borders around them and get bigger when selected(by making use of an UIImageView and swapping the images). Plus various animations when they first drop or when they get selected.

Therefore I'm making use of the -mapView:imageForAnnotation: method along with building on top of MGLAnnotationView class.

I've followed the following Mapbox tutorial and here is where I'm at currently:

func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
    // convert the given annotation to a VoiAnnotation
    guard let voiAnnotation = annotation as? VoiPointAnnotation else { return nil }

    // retrieve the annotation's category
    guard let annotationCategory = voiAnnotation.category else { return nil }

    var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationCategory)

    if annotationView == nil {
        annotationView = VoiAnnotationView(reuseIdentifier: annotationCategory)
    }

    return annotationView
}
class VoiAnnotationView: MGLAnnotationView {
    override func layoutSubviews() {
        super.layoutSubviews()

        prepareAnnotationView()
    }

    func prepareAnnotationView() {      
        let annotationIconName = "Annotation" + (reuseIdentifier?.firstLetterCapitalized())!
        let annotationIcon = UIImage(named: annotationIconName)
        let annotationIconView = UIImageView(image: annotationIcon)
        annotationIconView.layer.borderColor = Voi.colorPalette.white(.full).color.cgColor
        annotationIconView.layer.cornerRadius = annotationIconView.layer.frame.width / 2
        annotationIconView.layer.borderWidth = 0.1

        self.addSubview(annotationIconView)

        layer.cornerRadius = frame.width / 2
        layer.borderColor = Voi.colorPalette.white(.full).color.cgColor
        layer.borderWidth = 1.0
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        layer.borderWidth = 3.0

        // Animate the border width in/out, creating an iris effect.
        let animation = CABasicAnimation(keyPath: "borderWidth")
        animation.duration = 0.1
        layer.borderWidth = selected ? frame.width / 4 : 2
        layer.add(animation, forKey: "borderWidth")

        frame = CGRect(x: 0, y: 0, width: 44, height: 44)

        let annotationIconName = "Annotation\(reuseIdentifier?.firstLetterCapitalized())Enlarged"
        let annotationImage = UIImage(named: annotationIconName)
        let annotationImageView = UIImageView(image: annotationImage)

        addSubview(annotationImageView)
    }
}

My custom annotation views, VoiAnnotationView, seems to be appearing after a noticeable delay and when the map view gets moved around/zoomed, their respective locations on the screen does not get reflected as they should. Plus, the custom annotation views appearing with black boxes around even though I haven't defined them.

Also, I'm getting the following error message for each of the annotations I drop.

2017-05-05 16:48:47.077 Voicity[61221:3458099] This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
 Stack:(
    0   CoreFoundation                      0x0000000109964b0b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x0000000108c44141 objc_exception_throw + 48
    2   CoreFoundation                      0x00000001099cd625 +[NSException raise:format:] + 197
    3   Foundation                          0x000000010893d17b _AssertAutolayoutOnAllowedThreadsOnly + 105
    4   Foundation                          0x000000010876662f -[NSISEngine withBehaviors:performModifications:] + 31
    5   UIKit                               0x000000010b1c1b10 __57-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:]_block_invoke + 604
    6   UIKit                               0x000000010b1c188c -[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:] + 223
    7   UIKit                               0x000000010b1c1c0c __57-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:]_block_invoke_2 + 213
    8   Foundation                          0x00000001087666ab -[NSISEngine withBehaviors:performModifications:] + 155
    9   UIKit                               0x000000010b1c1b10 __57-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:]_block_invoke + 604
    10  UIKit                               0x000000010b1c188c -[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:] + 223
    11  UIKit                               0x000000010a8ad81f __45-[UIView(Hierarchy) _postMovedFromSuperview:]_block_invoke + 112
    12  UIKit                               0x000000010a8ad72d -[UIView(Hierarchy) _postMovedFromSuperview:] + 828
    13  UIKit                               0x000000010a8bd6ba -[UIView(Internal) _addSubview:positioned:relativeTo:] + 1927
    14  Mapbox                              0x00000001081e2c69 _Z35MGLUnitBezierForMediaTimingFunctionP21CAMediaTimingFunction + 74425
    15  Mapbox                              0x00000001081e24ef _Z35MGLUnitBezierForMediaTimingFunctionP21CAMediaTimingFunction + 72511
    16  Voicity                             0x0000000107b0401a _TFFC7Voicity5MapVC7mapViewFTCSo10MGLMapView23regionDidChangeAnimatedSb_T_U_FGSqGSaVS_8Location__T_ + 346
    17  Voicity                             0x0000000107b04708 _TTRXFo_oGSqGSaV7Voicity8Location____XFo_iGSqGSaS0_____ + 24
    18  Voicity                             0x0000000107b1085c _TFFC7Voicity10WebService4loadurFT8resourceGVS_8Resourcex_10completionFGSqx_T__T_U_FTGSqV10Foundation4Data_GSqCSo11URLResponse_GSqPs5Error___T_ + 684
    19  Voicity                             0x0000000107b10a68 _TPA__TFFC7Voicity10WebService4loadurFT8resourceGVS_8Resourcex_10completionFGSqx_T__T_U_FTGSqV10Foundation4Data_GSqCSo11URLResponse_GSqPs5Error___T_ + 216
    20  Voicity                             0x0000000107aacb9b _TTRXFo_oGSqV10Foundation4Data_oGSqCSo11URLResponse_oGSqPs5Error____XFdCb_dGSqCSo6NSData_dGSqS1__dGSqCSo7NSError___ + 203
    21  CFNetwork                           0x000000010c9d387b __75-[__NSURLSessionLocal taskForClass:request:uploadFile:bodyData:completion:]_block_invoke + 19
    22  CFNetwork                           0x000000010c9d3095 __49-[__NSCFLocalSessionTask _task_onqueue_didFinish]_block_invoke + 143
    23  Foundation                          0x000000010874e237 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
    24  Foundation                          0x000000010874df3b -[NSBlockOperation main] + 101
    25  Foundation                          0x000000010874c6f7 -[__NSOperationInternal _start:] + 627
    26  Foundation                          0x000000010874847c __NSOQSchedule_f + 198
    27  libdispatch.dylib                   0x000000010dabd05c _dispatch_client_callout + 8
    28  libdispatch.dylib                   0x000000010da9b94f _dispatch_queue_serial_drain + 221
    29  libdispatch.dylib                   0x000000010da9c669 _dispatch_queue_invoke + 1084
    30  libdispatch.dylib                   0x000000010da9eec4 _dispatch_root_queue_drain + 634
    31  libdispatch.dylib                   0x000000010da9ebef _dispatch_worker_thread3 + 123
    32  libsystem_pthread.dylib             0x000000010de54616 _pthread_wqthread + 1299
    33  libsystem_pthread.dylib             0x000000010de540f1 start_wqthread + 13
)

For a better understanding, here is a GIF:

Custom annotation views not behaving properly

Am I missing something here or is there a bug in the SDK? I've seen a similar issue here which I posted under it as well.

p.s. I recently updated to Mapbox-iOS-SDK 3.5.3; didn't help. I'm using the latest version of iOS along with Xcode. The above GIF is from the simulator but the situation applies to on device as well.

p.s. the Mapbox tutorial I mentioned above makes use of layoutSubviews method which as the Apple documentation states, should not be called directly but instead the method setNeedsLayout be called. What's the deal with that?

Platform: iOS Mapbox SDK version: 3.5.3

boundsj commented 7 years ago

Hi @cansurmeli

it's not happening at the moment, but I remember the custom annotation views appearing with black boxes around even though I haven't defined them

This does not sound like a Mapbox SDK issue. If you still see this please post a sample project or clear steps to reproduce so we can investigate. I did notice that in the code you pasted in this issue, you are setting annotationView = VoiAnnotationView and then immediately to annotationView = MGLAnnotationView so, in that example, you would not actually see VoiAnnotationViews. Perhaps you inadvertently enabled some view debugging feature in Xcode?

My custom annotation views, VoiAnnotationView, seems to be appearing after a noticeable delay

Can you define how long the delay is? Where are you testing (device or simulator) and what are other details of your development env (i.e. what kind of device and model, iOS version, Xcode version, etc.)? "Noticeable delay" is not enough information for us to make this issue actionable for that part of your report.

[annotations'] respective locations on the screen does not get reflected as they should

Can you please better define "as they should"? If possible please provide a .mov. gif (or similar) that illustrates the problem along with a project or sample code that reproduces the issue.

Thank you

cansurmeli commented 7 years ago

Thanks for the reply @boundsj.

I updated my original answer thinking it would be better for future references.

Sorry for the silly mistake of immediately reassigning my custom annotation view, VoiAnnotationView, with MGLAnnotationView. That was a debug thing I was doing myself.

Hope the edit sheds some more light.

boundsj commented 7 years ago

Thanks for the additional information @cansurmeli

I can see from the gif you supplied that you are definitely having issues using annotation views. Based on the error you are getting from iOS This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread it seems like you might be using multiple threads (i.e. downloading information from the network on a background thread that results in annotation views getting added on that background thread). You may want to audit your view controllers and make sure you are doing UI work on the main thread.

In any case, I'm still unable to reproduce the issues you are describing with the information you provided. Can you please attach an isolated sample app that reproduces this issue?

Also, there is an open PR (https://github.com/mapbox/mapbox-gl-native/pull/8926) to fix issues related to annotation views becoming detached when going offscreen (either because of a zero size annotation view or an annotation view that gets animated from a very small size to something larger). Of course you are welcome to try the fix in that PR but it looks like the issue you are experiencing won't be helped by that alone.

p.s. the Mapbox tutorial I mentioned above makes use of layoutSubviews method which as the Apple documentation states, should not be called directly but instead the method setNeedsLayout be called. What's the deal with that?

It is true that the Apple documentation states you should not call layoutSubviews directly. However, the Mapbox example you referenced does not call layoutSubviews. It overrides that method which is valid.

cansurmeli commented 7 years ago

Once I took my network call's completion handler to the main queue, everything got handled.

It never occurred to me this way. Silly mistake, actually. Thanks @boundsj. 👍🏻