I have a map pin view that represents an action.
This is the code
VStack {
ActionView(action: place.actions.first!)
.frame(width: 35, height: 35)
Text(self.displayPlaceName ? place.name : "")
}.overlay(
Image(systemName: "arrowtriangle.left.fill")
.rotationEffect(Angle(degrees: 270))
.foregroundColor(place.actions.first!.color())
.offset(y: 10)
)
The pin can represent only one action.
If there are two actions I would like to see half of both pin. something like that.
Thanks, Nicolas
One way to hide half of a view is by applying a gradient mask. If we put two stops in the gradient at the same location with different colors, we get an instant change (a step) in the gradient rather than a smooth transition. Some code:
struct ContentView: View {
var body: some View {
Callout(borderColor: .brown) {
Text("🍲")
}
.mask {
Rectangle()
.fill(.linearGradient(
stops: [
// The specific color doesn't matter here, only its alpha,
// which is 1 for .red and 0 for .clear.
.init(color: .red, location: 0),
.init(color: .red, location: 0.5),
.init(color: .clear, location: 0.5),
.init(color: .clear, location: 1.0),
],
startPoint: .leading,
endPoint: .trailing
))
}
}
}
Result:
We can swap the gradient colors to show the other half of the masked content. So, we can use a ZStack
containing two callouts, with opposite halves masked:
struct ContentView: View {
var body: some View {
ZStack {
Callout(borderColor: .brown) {
Text("🍲")
}
.mask {
Rectangle()
.fill(.linearGradient(
stops: [
// The specific color doesn't matter here, only its alpha,
// which is 1 for .red and 0 for .clear.
.init(color: .red, location: 0),
.init(color: .red, location: 0.5),
.init(color: .clear, location: 0.5),
.init(color: .clear, location: 1.0),
],
startPoint: .leading,
endPoint: .trailing
))
}
Callout(borderColor: .green) {
Text("📸")
}
.mask {
Rectangle()
.fill(.linearGradient(
stops: [
.init(color: .clear, location: 0),
.init(color: .clear, location: 0.5),
.init(color: .red, location: 0.5),
.init(color: .red, location: 1.0),
],
startPoint: .leading,
endPoint: .trailing
))
}
}
}
}
Result:
The result is a sharp cut between the two callouts. You can fade one into the other by replacing the step in the gradients with an interpolation:
struct ContentView: View {
var body: some View {
ZStack {
Callout(borderColor: .brown) {
Text("🍲")
}
.mask {
Rectangle()
.fill(.linearGradient(
stops: [
.init(color: .red, location: 0),
.init(color: .red, location: 0.4), // <<< CHANGED
.init(color: .clear, location: 0.6), // <<< CHANGED
.init(color: .clear, location: 1.0),
],
startPoint: .leading,
endPoint: .trailing
))
}
Callout(borderColor: .green) {
Text("📸")
}
.mask {
Rectangle()
.fill(.linearGradient(
stops: [
.init(color: .clear, location: 0),
.init(color: .clear, location: 0.4), // <<< CHANGED
.init(color: .red, location: 0.6), // <<< CHANGED
.init(color: .red, location: 1.0),
],
startPoint: .leading,
endPoint: .trailing
))
}
}
}
}
Result:
Here's the source for my Callout
view in case you want to play with it:
struct CalloutBorderShape: Shape {
let arrowHeight: CGFloat
func path(in rect: CGRect) -> Path {
let rect = rect.divided(atDistance: arrowHeight, from: .maxYEdge).remainder
let lineWidth: CGFloat = 3
var path = Path()
path.addRoundedRect(
in: rect.insetBy(dx: 0.5 * lineWidth, dy: 0.5 * lineWidth),
cornerSize: .init(width: 2 * lineWidth, height: 2 * lineWidth)
)
path = path.strokedPath(.init(lineWidth: lineWidth))
path.move(to: .init(x: rect.midX, y: rect.maxY + arrowHeight))
path.addLine(to: .init(x: rect.midX - arrowHeight, y: rect.maxY))
path.addLine(to: .init(x: rect.midX + arrowHeight, y: rect.maxY))
path.closeSubpath()
return path
}
}
struct Callout<Content: View>: View {
let borderColor: Color
@ViewBuilder
let content: Content
var body: some View {
content
.padding(EdgeInsets(
top: paddingSize,
leading: paddingSize,
bottom: paddingSize + arrowHeight,
trailing: paddingSize
))
.overlay {
CalloutBorderShape(arrowHeight: arrowHeight)
.fill(borderColor)
}
}
private let paddingSize: CGFloat = 10
private let arrowHeight: CGFloat = 6
}