Skip to content

Instantly share code, notes, and snippets.

@sebjvidal
Created July 30, 2025 08:59
Show Gist options
  • Save sebjvidal/ad873538e2780f64be8cbfb23baec2d8 to your computer and use it in GitHub Desktop.
Save sebjvidal/ad873538e2780f64be8cbfb23baec2d8 to your computer and use it in GitHub Desktop.
//
// 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()
}
@sebjvidal
Copy link
Author

sebjvidal commented Jul 30, 2025

Make sure to add these images to your asset catalog!

Explore-Dark Satellite Explore-Light

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment