|
// |
|
// ContentView.swift |
|
// MapModeButton-Demo |
|
// |
|
// Created by Seb Vidal on 30/07/2025. |
|
// |
|
|
|
import SwiftUI |
|
|
|
struct ContentView: View { |
|
@State var selection: MapMode? |
|
|
|
var body: some View { |
|
HStack(spacing: 16) { |
|
MapModeButton(mapMode: .explore, selection: $selection) |
|
|
|
MapModeButton(mapMode: .satellite, selection: $selection) |
|
} |
|
} |
|
} |
|
|
|
enum MapMode { |
|
case explore |
|
case satellite |
|
|
|
var title: String { |
|
switch self { |
|
case .explore: |
|
return "Explore" |
|
case .satellite: |
|
return "Satellite" |
|
} |
|
} |
|
|
|
var imageResource: ImageResource { |
|
switch self { |
|
case .explore: |
|
return .exploreMap |
|
case .satellite: |
|
return .satelliteMap |
|
} |
|
} |
|
} |
|
|
|
struct MapModeButton: View { |
|
private let cornerRadius: CGFloat = 15 |
|
private let innerBorderWidth: CGFloat = 0.75 |
|
private let outerBorderRadius: CGFloat = 0.5 |
|
|
|
let mapMode: MapMode |
|
@Binding var selection: MapMode? |
|
|
|
var body: some View { |
|
Button { |
|
selection = mapMode |
|
} label: { |
|
label |
|
} |
|
.buttonStyle( |
|
MapModeButtonStyle( |
|
title: mapMode.title, isSelected: selection == mapMode |
|
) |
|
) |
|
} |
|
|
|
var label: some View { |
|
Image(mapMode.imageResource) |
|
.resizable() |
|
.aspectRatio(contentMode: .fill) |
|
.frame(width: 81, height: 81) |
|
.clipShape( |
|
.rect(cornerRadius: cornerRadius) |
|
) |
|
.overlay { |
|
RoundedRectangle(cornerRadius: cornerRadius) |
|
.inset(by: innerBorderWidth / 2) |
|
.stroke(Color.white.opacity(0.25), lineWidth: innerBorderWidth) |
|
} |
|
.padding(outerBorderRadius) |
|
.background { |
|
Color.black |
|
.opacity(0.075) |
|
.cornerRadius(cornerRadius + outerBorderRadius) |
|
} |
|
} |
|
} |
|
|
|
struct MapModeButtonStyle: ButtonStyle { |
|
private var cornerRadius: CGFloat { |
|
isSelected ? 19 : 15 |
|
} |
|
|
|
private var strokeWidth: CGFloat { |
|
return isSelected ? 2 : 0 |
|
} |
|
|
|
private var strokeColor: Color { |
|
return isSelected ? .accentColor : .clear |
|
} |
|
|
|
let title: String |
|
let isSelected: Bool |
|
|
|
func makeBody(configuration: Configuration) -> some View { |
|
VStack(spacing: 8) { |
|
configuration.label |
|
.scaleEffect(isSelected ? 0.9 : 1) |
|
.overlay { |
|
RoundedRectangle(cornerRadius: cornerRadius) |
|
.inset(by: strokeWidth / 2) |
|
.stroke(strokeColor, lineWidth: strokeWidth) |
|
} |
|
.scaleEffect(configuration.isPressed ? 0.96 : 1) |
|
.animation(.default, value: isSelected) |
|
.animation(.default, value: configuration.isPressed) |
|
|
|
Text(title) |
|
.font(.subheadline) |
|
.fontWeight(.medium) |
|
} |
|
} |
|
} |
|
|
|
#Preview { |
|
ContentView() |
|
} |
Make sure to add these images to your asset catalog!