Giter Club home page Giter Club logo

Comments (32)

jose-roberto-abreu avatar jose-roberto-abreu commented on July 16, 2024 1

@BucekJiri Thanks for sharing that article, that explain a lot of things. 👏🏼

from map.

pauljohanneskraft avatar pauljohanneskraft commented on July 16, 2024

I'm not entirely sure why this is. I have experienced minor issues in this regard, but this seems to be off pretty much for all of the annotations and not even in the same direction, right? Could you maybe provide a minimal example project experiencing this issue, so that I could debug this? Would be greatly appreciated.

from map.

rderimay avatar rderimay commented on July 16, 2024

This is even stranger actually because sometimes when the app stars, this is ok, and and once when swapping maps back and forth, it becomes twisted with no change to the underlying data...

from map.

pauljohanneskraft avatar pauljohanneskraft commented on July 16, 2024

It's quite hard to predictably reproduce this issue for me - but I have a promising idea. Could you please try #18 whether it fixes that behavior?
If not, do you have some minimal example that I could use for debugging purposes? Would be much appreciated 😊

from map.

pauljohanneskraft avatar pauljohanneskraft commented on July 16, 2024

I just tried the most recent change on that branch and it worked without the issue appearing at all - if you notice this issue persists in future versions of Map, please let me know that this still persists/reappeared!

Closing this for now - will reopen, if the issue persists.

from map.

pauljohanneskraft avatar pauljohanneskraft commented on July 16, 2024

Reopening, since it doesn't seem to be fully solved. I was still able to reproduce this issue in certain cases. Not really sure what the issue here is - seems to be somewhat related to safe area insets, maybe?

from map.

rderimay avatar rderimay commented on July 16, 2024

Can also confirm that the last commit does not solve this issue.

from map.

rderimay avatar rderimay commented on July 16, 2024

I've created a stripped down test project where I can reproduce the problem. How can I send it to you @pauljohanneskraft ?

from map.

pauljohanneskraft avatar pauljohanneskraft commented on July 16, 2024

Thank you very much! You can send it as a zip right here by drag-and-dropping it in the text field, if you want.

from map.

rderimay avatar rderimay commented on July 16, 2024

Tester.zip

Here you go @pauljohanneskraft . You'll see that the white dots are not properly centered on the track (that could be me not setting them correctly), but they also rotate around a center slightly outside of them when you rotate the view for example. This offset is not constant from one app start to another...

from map.

pauljohanneskraft avatar pauljohanneskraft commented on July 16, 2024

Thank you very much! 😊

from map.

alex-reilly-pronto avatar alex-reilly-pronto commented on July 16, 2024

I've also been experiencing this issue, but it doesn't happen for all annotations. During the same run, some will be placed correctly, and some will be off. Creating a minimal example isn't going to be possible for me, but I'm down to try things.

from map.

pauljohanneskraft avatar pauljohanneskraft commented on July 16, 2024

@alex-reilly-pronto Thank you very much!

So what I have already figured out in the example above is, that the backwards support for MKAnnotations is used. In that case, the MKAnnotation instances should not be recreated during every run (this should probably be added to the Readme), since that would mean that in every run, the annotations are removed and the newly created ones are added to the underlying MKAnnotationView. You can get around this by simply creating @State properties in that view that are created the same way as they currently are in the initializer.

In addition to that, there seems to be an issue with ignoring the safe area - I will have a look at that as well. It seemingly only appears once the first one is "fixed". If you do not ignore the safe area for now, they should be at the place where they belong (which is ofc not a satisfactory solution, I know).

from map.

eugenijusr avatar eugenijusr commented on July 16, 2024

Is this maybe because ViewMapAnnotation is missing the anchorPoint property? If you don't specify it for MapAnnotation you have the same behavior - pins drifting as you zoom because of improper alignment. Specifying anchorPoint fixes it:

MapAnnotation(coordinate: coordinate, anchorPoint: .init(x: 0.5, y: 1.0)) {
  MyPinView()
}

ViewMapAnnotation doesn't have this property.

from map.

pauljohanneskraft avatar pauljohanneskraft commented on July 16, 2024

@eugenijusr It is right that the anchorPoint functionality is still missing here, it is not causing the issue though, as I pretty much hard code anchorPoint to be .init(x: 0.5, y: 0.5) - you can still get the behavior of the original version by making your view larger at the moment. I know that this is not exactly the same behavior, but it is close and should work for the most cases in the mean time.

@alex-reilly-pronto and @rderimay It would be really nice, if you could try to put .fixedSize() on the views that you are using inside the ViewMapAnnotation and also make sure to not recreate the MKAnnotation/MKCircle objects on each execution of the body (e.g. by wrapping it into an @State. With the onChange view modifier, you can still recreate this value on change of any underlying values.). Please let me know, if this solves the issue.

from map.

rderimay avatar rderimay commented on July 16, 2024

@pauljohanneskraft I changed MKAnnotation to a @State being calculated onAppear as they don't change over time, this is no problem.

As for the fixedSize() / .edgesIgnoringSafeArea(.all), I am having no success.
Removing .edgesIgnoringSafeArea(.all) completely doesn't even change the problem. I makes the offset smaller maybe, but that's all. I also tried to leave .edgesIgnoringSafeArea(.all) and to add .fixedSize() with a fixed frame(width:height:) for testing, but it does not change the behaviour.
Hope you can find the root of the problem soon...

from map.

BucekJiri avatar BucekJiri commented on July 16, 2024

We developed a similar library to yours. We also placed the view for the annotation into a HostingController and then added the HostingController as s subview to the MKAnnotationView. And we observed exactly the same bug with misplaced annotations.

This was solved by using the MKAnnotationView.image property instead of adding the view as a subview. The challenge here is transforming the view into a UIImage. I believe this can be done using an ImageRenderer (only for iOS 16) or passing a UIImage instead of a View somehow in the constructor.

We did not fix this fully and finish the library ourselves but maybe this info could help you somehow.

from map.

jose-roberto-abreu avatar jose-roberto-abreu commented on July 16, 2024

We developed a similar library to yours. We also placed the view for the annotation into a HostingController and then added the HostingController as s subview to the MKAnnotationView. And we observed exactly the same bug with misplaced annotations.

This was solved by using the MKAnnotationView.image property instead of adding the view as a subview. The challenge here is transforming the view into a UIImage. I believe this can be done using an ImageRenderer (only for iOS 16) or passing a UIImage instead of a View somehow in the constructor.

We did not fix this fully and finish the library ourselves but maybe this info could help you somehow.

I'm experiencing the same issue, only when the MKMapAnnotationView is used in conjunction with the HostingController (MKMarkerAnnotationView is working fine). The downside of converting to an UIImage, is that will lose the animation that I have set up on the SwiftUI View (i.e like pulse effect)

from map.

alex-reilly-pronto avatar alex-reilly-pronto commented on July 16, 2024

Have folks been able to predictably reproduce the bug? Could the annotation be manually offset to counteract it?

from map.

jose-roberto-abreu avatar jose-roberto-abreu commented on July 16, 2024

I can reproduce consistently with the following piece of code (as we zoom out, the circle shows up out of their coordinate center - the SwiftUI's Map is working fine in this scenario, in fact using a MKMarkerAnnotationView also works fine):

struct MapLocation: Identifiable {
    var id: String
    var coordinate: CLLocationCoordinate2D
}

struct ContentView: View {
    @State var locations: [MapLocation] = [
        MapLocation(id: "1", coordinate: CLLocationCoordinate2D(latitude: 18.473194, longitude: -69.934478))
    ]
    
    @State private var coordinateRegion = MKCoordinateRegion(
        center: .init(latitude: 18.478541, longitude: -69.924361),
        span: .init(latitudeDelta: 0.01, longitudeDelta: 0.01)
    )
    
    var body: some View {
        Map(coordinateRegion: $coordinateRegion, annotationItems: locations) { location in
            ViewMapAnnotation(coordinate: location.coordinate) {
                Circle()
                    .fill(.red)
                    .frame(width: 25, height: 25)
            }
        }
        .preferredColorScheme(.dark)
        .ignoresSafeArea()
    }
}

One note:

  • In the MKMapAnnotationView, The controller's preferredContentSize is always zero (somebody already opens a radar https://openradar.appspot.com/FB7650894). So, in this case, should be better to use the controller.view.intrisincContentSize or controller.view.subviews.first?.bounds.size

from map.

jose-roberto-abreu avatar jose-roberto-abreu commented on July 16, 2024

Updating the MKMapAnnotationView's setup method to this, has an interesting effect:

func setup(for mapAnnotation: ViewMapAnnotation<Content>) {
        annotation = mapAnnotation.annotation

        let controller = NativeHostingController(rootView: mapAnnotation.content)
        addSubview(controller.view)
        
        controller.view.sizeToFit()
        bounds.size = controller.view.intrinsicContentSize
        
        self.controller = controller
    }

IMG_5BA97A909324-1

^ The black square is the controller's view and it has the right position (no matter if you zoom in or out), but for some reason the content (SwiftUI View) is misplaced.

from map.

jose-roberto-abreu avatar jose-roberto-abreu commented on July 16, 2024

I think I found a solution, appliying this two changes on the MKMapAnnotationView make it work:

// MARK: Methods

    func setup(for mapAnnotation: ViewMapAnnotation<Content>) {
        annotation = mapAnnotation.annotation

        let controller = NativeHostingController(rootView: mapAnnotation.content)
        addSubview(controller.view)
        
        controller.view.sizeToFit()
        bounds.size = controller.view.intrinsicContentSize
        
        self.controller = controller
    }

    // MARK: Overrides

    override func layoutSubviews() {
        super.layoutSubviews()

        if let controller = controller {
            controller.view.subviews.forEach({ view in view.frame = controller.view.bounds })
        }
    }

^ This help to keep the annotation view position. But in my case, my SwiftUI's view has a pulse effect (and the ring circles are misplaced, for some reason those circles didn't reposition)

from map.

BucekJiri avatar BucekJiri commented on July 16, 2024

This completely breaks the subviews of your custom View that you pass in the ViewMapAnnotation.content property. It resizes the subviews to fill the HostingController bounds. I only see resized colored rectangles.

from map.

jose-roberto-abreu avatar jose-roberto-abreu commented on July 16, 2024

In the end, the changes that are working for me (including the animation on SwiftUI content) are this:

MKMapAnnotationView

    func setup(for mapAnnotation: ViewLocationDotMapAnnotation<Content>) {
        annotation = mapAnnotation.annotation
        let controller = UIHostingController(rootView: mapAnnotation.content())
        controller.view.backgroundColor = .clear
        addSubview(controller.view)
        
        controller.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            controller.view.centerXAnchor.constraint(equalTo: centerXAnchor),
            controller.view.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
        
        bounds.size = controller.view.subviews.max(by: { $0.bounds.contains($1.bounds) })?.bounds.size ?? .zero
        self.controller = controller
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        bounds.size = controller?.view?.subviews.max(by: { $0.bounds.contains($1.bounds) })?.bounds.size ?? .zero
    }

from map.

BucekJiri avatar BucekJiri commented on July 16, 2024

@Abreu0101 Thanks for this! I had to add the top and bottom constraints as well to make it work for my use case but then it worked all fine 👍

 func setup(for mapAnnotation: ViewLocationDotMapAnnotation<Content>) {
        annotation = mapAnnotation.annotation
        let controller = UIHostingController(rootView: mapAnnotation.content())
        controller.view.backgroundColor = .clear
        addSubview(controller.view)

        controller.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            controller.view.centerXAnchor.constraint(equalTo: centerXAnchor),
            controller.view.centerYAnchor.constraint(equalTo: centerYAnchor),
            controller.view.topAnchor.constraint(equalTo: topAnchor),
            controller.view.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])

        bounds.size = controller.view.subviews.max(by: { $0.bounds.contains($1.bounds) })?.bounds.size ?? .zero
        self.controller = controller
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        bounds.size = controller?.view?.subviews.max(by: { $0.bounds.contains($1.bounds) })?.bounds.size ?? .zero
    }

from map.

BucekJiri avatar BucekJiri commented on July 16, 2024

@Abreu0101 I take back what I wrote above. Your code made it better but did not solve the issue entirely for me. Upon investigating a bit closer I found out that the UIHostingController applies some random SafeAreaInsets to the misplaced annotations. This is not an uncommon bug, you can read on it here.

You need to disable the SafeArea behaviour for the UIHostingController. There is no native API for now. So the code that made the trick for me eventually is:

    private func updateContent(for selectedState: Bool) {
        guard let contentView = selectedState ? viewMapAnnotation?.selectedContent : viewMapAnnotation?.content else {
            return
        }
        controller?.view.removeFromSuperview()
        let controller = NativeHostingController(rootView: contentView, ignoreSafeArea: true)
        addSubview(controller.view)
        bounds.size = controller.preferredContentSize
        self.controller = controller
    }
    
    extension UIHostingController {
        /// This convenience init uses dynamic subclassing to disable safe area behaviour for a UIHostingController
        /// This solves bugs with embedded SwiftUI views having redundant insets
        /// More on this here: https://defagos.github.io/swiftui_collection_part3/
        /// - Parameters:
        ///   - rootView: The content View
        ///   - ignoreSafeArea: Disables the safe area insets if true
        convenience public init(rootView: Content, ignoreSafeArea: Bool) {
            self.init(rootView: rootView)
            
            if ignoreSafeArea {
                disableSafeArea()
            }
        }
        
        func disableSafeArea() {
            guard let viewClass = object_getClass(view) else { return }
            
            let viewSubclassName = String(cString: class_getName(viewClass)).appending("_IgnoreSafeArea")
            if let viewSubclass = NSClassFromString(viewSubclassName) {
                object_setClass(view, viewSubclass)
            }
            else {
                guard let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String else { return }
                guard let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0) else { return }
                
                if let method = class_getInstanceMethod(UIView.self, #selector(getter: UIView.safeAreaInsets)) {
                    let safeAreaInsets: @convention(block) (AnyObject) -> UIEdgeInsets = { _ in
                        return .zero
                    }
                    class_addMethod(viewSubclass, #selector(getter: UIView.safeAreaInsets),
                                    imp_implementationWithBlock(safeAreaInsets),
                                    method_getTypeEncoding(method))
                }
                
                objc_registerClassPair(viewSubclass)
                object_setClass(view, viewSubclass)
            }
        }
    }

from map.

alex-reilly-pronto avatar alex-reilly-pronto commented on July 16, 2024

@BucekJiri Your patch looks like it's working for me :) Thanks!

from map.

BucekJiri avatar BucekJiri commented on July 16, 2024

Btw you could just do controller._disableSafeArea = true but I am not sure this would make it through the App Store review since it is a private API and they probably scan for these.

from map.

rderimay avatar rderimay commented on July 16, 2024

@BucekJiri I can not get where to put you updateContent function in my SwiftUI view to make it working. If you could explain, it would be very nice. Thanks !

from map.

BucekJiri avatar BucekJiri commented on July 16, 2024

@rderimay You can check the PR for annotation selection where you can see how this is used in the MKMapAnnotationView.
This is not needed to fix the issue with incorrectly centered annotations though. The changes that fixed it have been already merged in this commit right before you asked.

from map.

rderimay avatar rderimay commented on July 16, 2024

@BucekJiri Thanks! I saw this but as there is no PR for the centring fix, I was a bit unsure how to integrate that in the code actually. I will probably make a fork to integrate it in my code quickly unless there is a better solution...

from map.

Terabyte1385 avatar Terabyte1385 commented on July 16, 2024

Updating the MKMapAnnotationView's setup method to this, has an interesting effect:

func setup(for mapAnnotation: ViewMapAnnotation<Content>) {
        annotation = mapAnnotation.annotation

        let controller = NativeHostingController(rootView: mapAnnotation.content)
        addSubview(controller.view)
        
        controller.view.sizeToFit()
        bounds.size = controller.view.intrinsicContentSize
        
        self.controller = controller
    }

IMG_5BA97A909324-1

^ The black square is the controller's view and it has the right position (no matter if you zoom in or out), but for some reason the content (SwiftUI View) is misplaced.

I'm experiencing the same thing. My assumption is that the circle in your screenshot is indeed centered, but centered inside the safe area, while the map view extends outside of it.

from map.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.