Button
Definition
Button ist eine interaktive SwiftUI View, die eine Aktion ausführt, wenn der Benutzer darauf tippt. Buttons sind essentiell für User-Interaktionen in jeder App.
Basic Syntax
// Einfacher Button
Button("Tap Me") {
print("Button tapped")
}
// Mit Label und Action
Button(action: {
print("Action performed")
}) {
Text("Click Here")
}
Button mit Text
// Standard Button
Button("Save") {
saveData()
}
// Mit mehreren Text-Zeilen
Button {
submitForm()
} label: {
Text("Submit")
Text("Form")
}
Button mit Icon
// Nur Icon
Button {
refresh()
} label: {
Image(systemName: "arrow.clockwise")
}
// Icon mit Text
Button {
addItem()
} label: {
Label("Add", systemImage: "plus")
}
// Custom Layout
Button {
share()
} label: {
HStack {
Image(systemName: "square.and.arrow.up")
Text("Share")
}
}
Button Styles
Built-in Styles
// Automatic (Standard)
Button("Automatic") {
// action
}
// Bordered
Button("Bordered") {
// action
}
.buttonStyle(.bordered)
// Bordered Prominent
Button("Prominent") {
// action
}
.buttonStyle(.borderedProminent)
// Borderless
Button("Borderless") {
// action
}
.buttonStyle(.borderless)
// Plain
Button("Plain") {
// action
}
.buttonStyle(.plain)
Button Roles
// Destructive Action (rot)
Button("Delete", role: .destructive) {
deleteItem()
}
// Cancel Action
Button("Cancel", role: .cancel) {
dismiss()
}
// Normal Action
Button("Save") {
save()
}
Custom Button Styling
// Mit Modifiers
Button("Custom") {
performAction()
}
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
// Komplexes Custom Design
Button {
login()
} label: {
Text("Login")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(
LinearGradient(
colors: [.blue, .purple],
startPoint: .leading,
endPoint: .trailing
)
)
.cornerRadius(12)
.shadow(radius: 5)
}
Custom Button Style (Wiederverwendbar)
// Custom Style definieren
struct PrimaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(10)
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
}
}
// Verwendung
Button("Save") {
save()
}
.buttonStyle(PrimaryButtonStyle())
// Extension für einfachere Verwendung
extension ButtonStyle where Self == PrimaryButtonStyle {
static var primary: PrimaryButtonStyle {
PrimaryButtonStyle()
}
}
Button("Save") {
save()
}
.buttonStyle(.primary)
Button States
struct ContentView: View {
@State private var isProcessing = false
var body: some View {
Button {
processData()
} label: {
if isProcessing {
ProgressView()
.progressViewStyle(.circular)
.tint(.white)
} else {
Text("Submit")
}
}
.disabled(isProcessing)
}
func processData() {
isProcessing = true
// Simulate async work
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
isProcessing = false
}
}
}
Disabled Buttons
struct FormView: View {
@State private var email = ""
@State private var password = ""
private var isValid: Bool {
!email.isEmpty && password.count >= 6
}
var body: some View {
VStack {
TextField("Email", text: $email)
SecureField("Password", text: $password)
Button("Login") {
login()
}
.disabled(!isValid)
.opacity(isValid ? 1.0 : 0.5)
}
}
func login() {
print("Logging in...")
}
}
Button mit Confirmation Dialog
struct DeleteView: View {
@State private var showConfirmation = false
var body: some View {
Button("Delete", role: .destructive) {
showConfirmation = true
}
.confirmationDialog("Are you sure?", isPresented: $showConfirmation) {
Button("Delete", role: .destructive) {
performDelete()
}
Button("Cancel", role: .cancel) {}
}
}
func performDelete() {
print("Deleted")
}
}
Button Groups
// Horizontal Button Group
HStack(spacing: 15) {
Button("Cancel") {
cancel()
}
.buttonStyle(.bordered)
Button("Save") {
save()
}
.buttonStyle(.borderedProminent)
}
// Vertical Button Group
VStack(spacing: 10) {
Button("Option 1") { selectOption(1) }
Button("Option 2") { selectOption(2) }
Button("Option 3") { selectOption(3) }
}
.buttonStyle(.bordered)
Icon Buttons
// Toolbar Icon Button
Button {
toggleFavorite()
} label: {
Image(systemName: isFavorite ? "heart.fill" : "heart")
.foregroundColor(isFavorite ? .red : .gray)
}
// Circle Icon Button
Button {
addItem()
} label: {
Image(systemName: "plus")
.font(.title2)
.foregroundColor(.white)
.frame(width: 50, height: 50)
.background(Color.blue)
.clipShape(Circle())
.shadow(radius: 3)
}
// Square Icon Button
Button {
openSettings()
} label: {
Image(systemName: "gearshape.fill")
.font(.title3)
.foregroundColor(.white)
.frame(width: 44, height: 44)
.background(Color.gray)
.cornerRadius(10)
}
Button mit Animation
struct AnimatedButton: View {
@State private var isPressed = false
var body: some View {
Button {
withAnimation(.spring(response: 0.3)) {
isPressed.toggle()
}
performAction()
} label: {
Text("Tap Me")
.font(.headline)
.foregroundColor(.white)
.padding()
.background(isPressed ? Color.green : Color.blue)
.cornerRadius(10)
.scaleEffect(isPressed ? 1.1 : 1.0)
}
}
func performAction() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
isPressed = false
}
}
}
Praktische Beispiele
Login Button
Button {
login()
} label: {
HStack {
Image(systemName: "person.fill")
Text("Sign In")
}
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(12)
}
Download Button mit Progress
struct DownloadButton: View {
@State private var isDownloading = false
@State private var progress: Double = 0
var body: some View {
Button {
startDownload()
} label: {
ZStack {
if isDownloading {
ProgressView(value: progress)
.progressViewStyle(.linear)
} else {
Label("Download", systemImage: "arrow.down.circle")
}
}
.frame(width: 200)
}
.disabled(isDownloading)
}
func startDownload() {
isDownloading = true
progress = 0
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
progress += 0.1
if progress >= 1.0 {
timer.invalidate()
isDownloading = false
}
}
}
}
Toggle Button
struct ToggleButton: View {
@State private var isActive = false
var body: some View {
Button {
isActive.toggle()
} label: {
HStack {
Image(systemName: isActive ? "checkmark.circle.fill" : "circle")
Text(isActive ? "Active" : "Inactive")
}
.foregroundColor(isActive ? .green : .gray)
.padding()
.frame(maxWidth: .infinity)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(isActive ? Color.green : Color.gray, lineWidth: 2)
)
}
}
}
Counter Button
struct CounterButton: View {
@State private var count = 0
var body: some View {
HStack(spacing: 20) {
Button {
count -= 1
} label: {
Image(systemName: "minus.circle.fill")
.font(.title)
}
Text("\(count)")
.font(.title)
.frame(width: 60)
Button {
count += 1
} label: {
Image(systemName: "plus.circle.fill")
.font(.title)
}
}
}
}
Card Action Button
Button {
performAction()
} label: {
VStack(alignment: .leading, spacing: 10) {
Image(systemName: "star.fill")
.font(.largeTitle)
.foregroundColor(.yellow)
Text("Premium Feature")
.font(.headline)
Text("Unlock premium features")
.font(.caption)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color.white)
.cornerRadius(15)
.shadow(radius: 5)
}
.buttonStyle(.plain)
Floating Action Button (FAB)
struct ContentView: View {
var body: some View {
ZStack {
// Main content
List(1..<20) { index in
Text("Item \(index)")
}
// FAB
VStack {
Spacer()
HStack {
Spacer()
Button {
addItem()
} label: {
Image(systemName: "plus")
.font(.title2)
.foregroundColor(.white)
.frame(width: 60, height: 60)
.background(Color.blue)
.clipShape(Circle())
.shadow(radius: 5)
}
.padding()
}
}
}
}
func addItem() {
print("Add new item")
}
}
Segmented Control Style
struct SegmentedButton: View {
@State private var selection = 0
let options = ["Day", "Week", "Month"]
var body: some View {
HStack(spacing: 0) {
ForEach(0..<options.count, id: \.self) { index in
Button {
selection = index
} label: {
Text(options[index])
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
.background(selection == index ? Color.blue : Color.clear)
.foregroundColor(selection == index ? .white : .blue)
}
}
}
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.blue, lineWidth: 1)
)
}
}
Button in Navigation
// Navigation Link als Button
NavigationLink {
DetailView()
} label: {
Text("Go to Details")
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
// Button mit programmatic navigation
struct ContentView: View {
@State private var isActive = false
var body: some View {
NavigationStack {
VStack {
Button("Navigate") {
isActive = true
}
NavigationLink(destination: DetailView(), isActive: $isActive) {
EmptyView()
}
}
}
}
}
Best Practices
- Klare Labels: Beschreibende Button-Texte
// ✅ Gut
Button("Save Changes") { save() }
// ❌ Schlecht
Button("OK") { save() }
- Feedback geben: Visuelles Feedback bei Interaktion
Button("Submit") { submit() }
.scaleEffect(isPressed ? 0.95 : 1.0)
- Disabled State visualisieren
Button("Save") { save() }
.disabled(!isValid)
.opacity(isValid ? 1.0 : 0.5)
- Roles verwenden für wichtige Actions
Button("Delete", role: .destructive) { delete() }
Common Use Cases
- Forms: Submit, Save, Cancel Buttons
- Navigation: Back, Next, Done Buttons
- Actions: Delete, Edit, Share Buttons
- Toggles: On/Off, Favorite, Like Buttons
- Counters: Plus/Minus Buttons
- Toolbar: Icon Action Buttons
- Cards: Call-to-Action Buttons
Related Topics
- ButtonStyle: Custom Button Styles
- Gestures: Tap, Long Press Gestures
- Navigation: NavigationLink
- Alerts: Confirmation Dialogs