# Button
버튼은 탭 이후의 동작을 action
파라미터에 클로저로 정의하고 UI 구성을 label
파라미터에서 정의한다. 레이블 뷰가 단순 텍스트로만 이루어진 경우 Button(title: StringProtocol, action: () -> Void)
로 선언하면 된다. (title
파라미터에는 문자열 전달)
Button {
value = Int.random(in: 0...100)
} label: {
Text("Generate")
}
.padding()
// 위 아래 동일
Button("Generate") {
value = Int.random(in: 1...100)
}
.padding()
label
파라미터에는 텍스트만 전달할 필요가 없고 이미지도 함께 전달 가능하다. SF Symbol
이미지를 사용하면 텍스트뷰와 자동 정렬되어 스택뷰에 임베딩하지 않아도 된다.
Button {
value = Int.random(in: 0...100)
} label: {
Image(systemName: "repeat")
Text("Generate")
}
.padding()
버튼에 둥근 모서리를 부여하기 위해서 .cornerRadius
모디파이어를 사용해도 되지만 나중의 iOS 버전에서 deprecated
될 예정이기에 .clipShape
모디파이어를 사용한다.
Button {
value = Int.random(in: 0...100)
} label: {
Image(systemName: "repeat")
Text("Generate")
}
.padding()
.clipShape(.rect(cornerSize: .init(width: 20, height: 20)))
직접 버튼 스타일을 정의해도 되지만 SwiftUI
에서 제공하는 기본 스타일들이 있다. .buttonStyle
모디파이어를 통해 조절 가능하다.
.automatic
케이스는 버튼 뷰가 임베딩되는 컨텍스트에 맞춰 자동으로 스타일링을 진행한다. List
, ContextMenu
와 같은 뷰에서 사용하게 되면 버튼 스타일이 자동으로 맞춰진다.
Button("Automatic", action: {})
.padding()
.buttonStyle(.automatic)
.automatic
케이스 외에도 .plain
, .bordered
와 같은 다양한 스타일들이 존재하는데, 이러한 기본 스타일을 버튼에 적용하게 되면 .tint
모디파이어에 컬러를 전달했을때 내부적으로 미리 정의된 컬러셋이 버튼 전체에 적용된다.
# Link
Link
뷰는 외부 URL로 연결할 때 사용한다. 버튼 액션에 UIApplication.shared.open
메서드를 추가하여 직접 구현해도 되지만 Link
뷰를 사용하면 뷰에 표시할 텍스트와 URL
객체만 전달해주면 된다.
Button("Apple Developer") {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
.padding()
Link("Apple Developer", destination: url)
웹사이트 URL만 적용 가능한 것이 아니라 iOS에서 제공하는 URL Scheme
에 따라 자동으로 연결된다. 핸드폰 번호를 전달하면 자동으로 메신저 앱이 실행된다.
Link("Apple Developer", destination: "010-....-....")
Link
에 .environment
모디파이어 키패스 중 \.openURL
을 지정하고, 파라미터에 OpenURLAction
객체를 정의하면 URL 처리에 대한 동작을 커스텀 할 수 있다.
Link(destination: sms) {
Label("Apple Developer", systemImage: "house")
}
.padding()
.background(.ultraThinMaterial, in: .rect(cornerRadius: 12))
.environment(\.openURL, OpenURLAction { url in
if 동작_1 {
return .handled
} else if 동작_1 {
return .discarded
} else if 동작_2 {
return .systemAction(_ url: URL)
}
return .systemAction
})
OpenURLAction
객체의 handler
클로저 리턴타입은 OpenURLAction.Result
구조체이다.
public struct Result {
public static let handled: OpenURLAction.Result
public static let discarded: OpenURLAction.Result
public static let systemAction: OpenURLAction.Result
public static func systemAction(_ url: URL) -> OpenURLAction.Result
}
처리 로직에 따라 결과를 리턴하면 그에 맞게 링크가 동작하게 된다.
.handled
: 정상적으로 URL 처리가 완료된 경우를 시스템에 알리기 위함이며 딥링크 연결 및 사파리 오픈을 위한 동작을 구현한 뒤.handled
를 리턴하면 된다..discarded
: URL처리에 문제가 있음을 알리기 위함이다..systemAction
: 동작에 대한 처리를 시스템에게 위임한다. (URL 스킴 구성에 따라 내부적으로 판단됨)
# Menu
SwiftUI에서 제공하는 메뉴 UI는 다음과 같이 생겼다.
메뉴 내에는 일반적으로 Button
을 임베딩하고 버튼 label
클로저에 Label
뷰를 임베딩하여 이미지를 추가할 수도 있다. 또한 메뉴 내에 메뉴를 계층적으로 구성할 수도 있다.
Menu
는 primaryAction
클로저에 동작을 정의할 수 있다. primaryAction
을 정의하는 경우 메뉴 탭의 기본 액션이 실행되지 않고 클로저 내의 동작이 실행된다. 메뉴를 오랫동안 탭하고 있으면 메뉴 내의 요소들이 노출되는 방식으로 동작한다.
# Toggle
Toggle
뷰는 Binding<Bool>
타입의 상태값을 받는다.
@State private var isOn = false
VStack(spacing: 30) {
Image(systemName: isOn ? "lightbulb.fill" : "lightbulb")
.foregroundColor(isOn ? .yellow : .gray)
// #1
Toggle("ON", isOn: $isOn)
.padding()
}
토글 레이블과 컨트롤러 배치에 대한 커스텀 모디파이어가 아직은 제공되지 않기 때문에 직접 세로 배치와 같이 커스텀하고 싶으면 직접 VStack
임베딩 해야한다.
VStack {
Toggle(isOn: $isOn, label: {
EmptyView()
})
.padding()
.labelsHidden()
Label("On/Off", systemImage: "bolt")
}
# Slider
Slider
는 BinaryFloatingPoint
바인딩 속성을 갖는다. in
파라미터에 값의 하한과 상한을 설정하지 않으면 기본적으로 0과 1 사이의 값으로 설정된다. label
클로저에서는 뷰를 리턴해도 iOS에서 보이지 않는다.
@State private var dragging = false
Button("Reset") {
r = 0.0
g = 0.0
b = 0.0
}
.buttonStyle(.borderedProminent)
.disabled(dragging)
Slider(value: $g, in: 0...255, label: {
EmptyView()
}, minimumValueLabel: {
Text("G")
.foregroundStyle(.green)
}, maximumValueLabel: {
Text("\(Int(g))")
}, onEditingChanged: { editing in
dragging = editing
})
.padding()
onEditingChanged
클로저의 파라미터에는 드래그 여부 불리언값이 전달된다. 드래그중이면 true, 드래그를 마치면 false가 전달된다.
miminumValueLabel
클로저와 maximumValueLabel
클로저에서 리턴하는 뷰는 동일해야 한다. Text
에 frame
모디파이어를 추가하면 타입 추론에 따라 View
를 리턴하는 것으로 인식되어 컴파일 에러가 발생하므로 한쪽에 frame
모디파이어를 추가한다면 반대쪽에도 추가해줘야 한다.
# ProgressView
진행상황을 나타내는 ProgressView
는 대표적으로 Circular Style
과 Linear Style
로 나뉜다. 따로 모디파이어를 제공하는 것은 아니고 다른 파라미터를 받는 생성자 함수를 호출하면 된다.
Circular
스타일은 익히 알고 있는 UIActivityIndicator
이다. Linear Style
은 선형으로 게이지가 차는 모습의 프로그레스 뷰이다.
Linear
스타일은 값의 변화를 표기만 하고 능동적인 값의 처리는 하지 않기 때문에 바인딩 속성을 받는 것이 아닌 실제 값을 받는다.
ProgressView(value: progress) {
Label("Download", systemImage: "icloud.and.arrow.down")
} currentValueLabel: {
Text("\(Int(progress))")
}
.padding()
선형 프로그레스 뷰는 value
, label
, currentValueLabel
을 정의하면 된다.
# Stepper
Stepper
는 +, - 컨트롤러를 가지고 값을 조작해준다.
Stepper("Quantity", value: $quantity, in: 0...100, step: 1) { editing in
}
.padding()
위와 같이 @State
변수를 받는 형태로 정의해도 되고
Stepper("Quantity") {
if quantity >= 5 {
quantity = 0
} else {
quantity += 1
}
} onDecrement: {
if quantity <= 0 {
quantity = 5
} else {
quantity -= 1
}
}
이런 식으로 onIncrement
와 onDecrement
에서 커스텀 로직을 작성해도 된다. 레이블과 바인딩 변수를 파라미터로 받는 경우 value와 step은 기본값이 설정되어 있다.
# Picker
Picker
를 통해 피커뷰를 구현할 수 있다. Identifiable
, CaseIterable
프로토콜을 채택한 열거형을 전달하거나 Identifiable
로 각 값을 구분 가능한 아이디 속성이 있으면 된다.
selection
파라미터에 각 값을 바인딩 속성으로 전달하면 된다.
Picker("Favorite", selection: $selected) {
Text("Soccer").tag(Sports.soccer)
Text("Baseball").tag(Sports.baseball)
Text("Baseketball").tag(Sports.basketball)
}
이런식으로 내부에 요소 여러개를 전달할때 각 row당 표시할 데이터를 고유하게 구분해야 하므로 구분되는 selection
으로 전달되는 고유 값을 tag
에 전달하면 된다.
요소가 많아지는 경우 매번 tag
속성에 값을 직접 전달하는 것이 번거롭기 때문에 ForEach
를 통해 간단히 구현 가능하다. ForEach
에 전달된 데이터 모델이 Identifable
을 채택하고 있으면 id값을 통한 구분이 자동으로 이루어지므로 반복된 로직을 직접 작성할 필요가 없다.
Picker("Favorite", selection: $selected) {
ForEach(Sports.allCases) { item in
Text(item.rawValue)
}
}
.pickerStyle(.wheel)
또한 피커를 List
에 임베딩하면 컨텍스트에 맞춰 자동으로 UI가 조정된다.
List {
Text(selected.rawValue)
.font(.system(size: 200))
Picker("Favorite", selection: $selected) {
ForEach(Sports.allCases) { item in
Text(item.rawValue)
}
}
.pickerStyle(.inline)
}
# DatePicker
DatePicker("날짜 선택", selection: $selectedDate, displayedComponents: [.date, .hourAndMinute])
.padding()
.datePickerStyle(.wheel)
.labelsHidden()
DatePicker
로 데이터피커를 구현할 수 있다. 피커뷰와 유사하게 selection
에 Date
타입 바인딩 속성을 전달한다. displayedComponents
에 값을 전달하여 데이트피커 표시 데이터를 지정할 수 있다.
datePickerStyle
모디파이어로 명시적으로 데이트피커 스타일을 지정할 수 있다. OS별로 표시 기본 스타일이 달라질 수 있으므로 명시적으로 표시해두는게 좋다.
wheel
의 경우 레이블이 찌그러질 수 있으므로 레이블은 숨기는게 좋다.
# ColorPicker
ColorPicker
로 컬러피커를 구현할 수 있다.
ColorPicker("컬러 선택", selection: $backgroundColor, supportsOpacity: true)
Color타입의 바인딩 속성을 selection
에 전달하면 된다. UI 커스텀은 불가능하고 레이블 labelIsHidden
모디파이어만 적용 가능하다.