I'm using Swift and MapKit, and I want to show popup like this (when you click on the marker) :
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 ?
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.
By applying .fixedView()
to the container holding the header and the lines of text, each of the text lines will be shown in full on one line, without wrapping or truncation.
The overall view can be allowed to find its own size, there is no need to set any fixed width or height.
The only slightly tricky part is the brown background behind the header, because this needs to extend to the full width of the container. One way to implement is as the "header" for the background. A hidden Text
element (showing just "X") can be used to determine the height.
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)
}
}