Comments (32)
@BucekJiri Thanks for sharing that article, that explain a lot of things. 👏🏼
from map.
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.
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.
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.
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.
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.
Can also confirm that the last commit does not solve this issue.
from map.
I've created a stripped down test project where I can reproduce the problem. How can I send it to you @pauljohanneskraft ?
from map.
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.
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.
Thank you very much! 😊
from map.
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.
@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.
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.
@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.
@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.
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.
We developed a similar library to yours. We also placed the view for the annotation into a
HostingController
and then added theHostingController
as s subview to theMKAnnotationView
. 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 aUIImage
. I believe this can be done using anImageRenderer
(only for iOS 16) or passing aUIImage
instead of aView
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.
Have folks been able to predictably reproduce the bug? Could the annotation be manually offset to counteract it?
from map.
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
orcontroller.view.subviews.first?.bounds.size
from map.
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
}
^ 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.
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.
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.
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.
@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.
@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.
@BucekJiri Your patch looks like it's working for me :) Thanks!
from map.
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.
@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.
@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.
@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.
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 }
^ 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)
- Map Annotation selection HOT 1
- ViewMapAnnotation does not allow text HOT 2
- ViewMapAnnotation not updating location HOT 1
- Using current zoom factor in Annotations HOT 1
- ViewMapAnnotation doesn't support clustering HOT 4
- Support with documentation HOT 4
- Package not recognised HOT 18
- impossible to have my liste of annotations HOT 1
- Issue with userTrackingMode HOT 8
- User location not showed HOT 6
- Map+Coordinator.swift:256 Modifying state during view update, this will cause undefined behavior.
- onTapGesture not working HOT 23
- watchOS support for overlays? HOT 2
- assertionFailure("Somehow a cluster contains an unknown annotation item.") HOT 8
- Fails to compile for macOS (Apple Silicon) HOT 2
- If an annotation is clustered and then this annotation is removed from the array of annotationItems, it throws error assertionFailure("Somehow a cluster contains an unknown annotation item." HOT 1
- Append a Text after an Image as ViewMapAnnotation not showed HOT 1
- Annotations aren't immobile and move with zoom HOT 4
- Running HOT 1
- can't use Map on VisionOS. error about userTrackingMode HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from map.