swiftmapkit

How to make annotation popup without using fixed frame width/height?


I'm using Swift and MapKit, and I want to show popup like this (when you click on the marker) : enter image description here

With:

So I made a map like that:

var body: some View {
    Map {
        ForEach(sessions) { session in
            Annotation("", coordinate: session.getCoordinate()) {
                // Here I put the content of annotation (check below)
            }
        }
    }
}

Now, I'm trying to build the annotation. Here is my current code :

GeometryReader { proxy in
    VStack {
        VStack {
            HStack(spacing: 10) {
                Text("header")
                    .foregroundStyle(.black)
            }.padding(.horizontal).frame(minWidth: proxy.size.width).background(.brown)
            Text("Here: yes")
            Text("Line: second")
            Text("Some: Infos")
            Text("Stack: overflow")
        }.background(.white).opacity(selected?.SessionId == session.SessionId ? 1 : 0)
        Image(systemName: "mappin")
            .resizable()
            .frame(width: 16, height: 32)
            .foregroundColor(session.getColor())
            .shadow(radius: 4)
    }.onTapGesture {
        selected = (selected?.SessionId == session.SessionId ? nil : session)
    }   
}.frame(width: 120, height: 80)

My problem is that I don't want to set manually width and height as I don't know which size will be the mobile phone and the width taken by text will also change. I'm using GeometryReader to be able to use proxy.size.width which help me the make the header text take the full width.

I tried to ignoresSafeArea(.all) instead of using frame but the annotation doesn't appear at all

How can I fix it ?


Solution

  • You said in a comment that:

    Each line is one line that should not go on new line

    This helps a lot, because it means that the width of the view is determined by either the header or the longest full line, whichever is the widest.

    Here is an example to show it working. I have ignored the fact that the view will be shown in the context of a map Annotation, hopefully it is not relevant for the layout.

    struct MultiLineAnnotation: View {
        let header: String
        let textLines: [String]
    
        var body: some View {
            VStack {
                VStack(alignment: .leading) {
                    Text(header)
                        .foregroundStyle(.white)
                    ForEach(Array(textLines.enumerated()), id: \.offset) { _, line in
                        Text(line)
                    }
                }
                .fixedSize()
                .padding(.horizontal)
                .background {
                    VStack(spacing: 0) {
                        Text("X")
                            .hidden()
                            .frame(maxWidth: .infinity)
                            .background(.brown)
                        Rectangle()
                            .fill(.background)
                    }
                }
                Image(systemName: "mappin")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 16, height: 32)
                    .foregroundStyle(.orange)
                    .shadow(radius: 4)
            }
        }
    }
    

    Example use:

    struct ContentView: View {
        let header = "Header"
        let hereIsSomeText = "Here is some text"
        let theQuickBrownFox = ["The quick brown", "fox jumps over", "the lazy dog"]
        let loremIpsum = ["Lorem ipsum dolor sit amet,", "consectetur adipiscing elit,", "sed do eiusmod tempor", "incididunt ut labore et", "dolore magna aliqua."]
    
    
        var body: some View {
            VStack(spacing: 50) {
                MultiLineAnnotation(header: hereIsSomeText, textLines: theQuickBrownFox)
                MultiLineAnnotation(header: header, textLines: theQuickBrownFox)
                MultiLineAnnotation(header: header, textLines: loremIpsum)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(.yellow)
        }
    }
    

    Screenshot