Skip to main content

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

  1. Klare Labels: Beschreibende Button-Texte
// ✅ Gut
Button("Save Changes") { save() }

// ❌ Schlecht
Button("OK") { save() }
  1. Feedback geben: Visuelles Feedback bei Interaktion
Button("Submit") { submit() }
.scaleEffect(isPressed ? 0.95 : 1.0)
  1. Disabled State visualisieren
Button("Save") { save() }
.disabled(!isValid)
.opacity(isValid ? 1.0 : 0.5)
  1. 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
  • ButtonStyle: Custom Button Styles
  • Gestures: Tap, Long Press Gestures
  • Navigation: NavigationLink
  • Alerts: Confirmation Dialogs