Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- uitableview
- Observable
- 리펙토링
- SWIFT
- ios
- uiscrollview
- swiftUI
- ribs
- collectionview
- UICollectionView
- clean architecture
- RxCocoa
- Xcode
- 리펙터링
- combine
- Refactoring
- Protocol
- UITextView
- MVVM
- 클린 코드
- Human interface guide
- swift documentation
- Clean Code
- 스위프트
- tableView
- HIG
- 애니메이션
- 리팩토링
- rxswift
- map
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swiftUI] ButtonStyle을 이용한 커스텀 버튼 구현 방법 (ButtonStyle, PrimitiveButtonStyle) 본문
iOS 기본 (SwiftUI)
[iOS - swiftUI] ButtonStyle을 이용한 커스텀 버튼 구현 방법 (ButtonStyle, PrimitiveButtonStyle)
jake-kim 2022. 8. 6. 23:09* Button 기본 개념은 이전 포스팅 글 참고
커스텀 버튼 구현 아이디어
- ButtonStyle을 준수하는 struct형을 만들고, makeBody(configuration:) 메소드를 구현
- SwiftUI에서는 상속사용을 지양하기 때문에, 사용하는 쪽에서 .buttonStyle()으로 사용
// 커스텀 ScaleButton을 만들기 위해서 ButtonStyle 준수하고 makeBody 메서드 구현
struct ScaleButton: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
<#code#>
}
}
- makeBody 인자 configuration은 label 프로퍼티에 접근할수 있는데, 이 label 인스턴스를 가지고 커스텀
- isPressed 속성도 존재하여, 버튼 탭 애니메이션도 쉽게 적용이 가능
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public struct ButtonStyleConfiguration {
/// A type-erased label of a button.
public struct Label : View {
public typealias Body = Never
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
public let role: ButtonRole?
/// A view that describes the effect of pressing the button.
public let label: ButtonStyleConfiguration.Label
/// A Boolean that indicates whether the user is currently pressing the button
public let isPressed: Bool
}
EdgeInsets Extension만들기
- 커스텀 UI를 만들기 전에 EdgeInsets을 쉽게 사용하기위해 EdgeInsets 확장
extension EdgeInsets {
var horizontalInsets: CGFloat { self.trailing + self.leading }
var verticalInsets: CGFloat { self.top + self.bottom }
var left: CGFloat { self.leading }
var right: CGFloat { self.trailing }
static func with(top: CGFloat = 0, left: CGFloat = 0, bottom: CGFloat = 0, right: CGFloat = 0) -> EdgeInsets {
EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right)
}
static func horizontal(_ horizontal: CGFloat, top: CGFloat = 0, bottom: CGFloat = 0) -> UIEdgeInsets {
UIEdgeInsets(
top: top,
left: horizontal,
bottom: bottom,
right: horizontal
)
}
static func vertical(_ vertical: CGFloat, left: CGFloat = 0, right: CGFloat = 0) -> UIEdgeInsets {
UIEdgeInsets(
top: vertical,
left: left,
bottom: vertical,
right: right
)
}
init(_ all: CGFloat) {
self = EdgeInsets(top: all, leading: all, bottom: all, trailing: all)
}
init(top: CGFloat = 0, left: CGFloat = 0, bottom: CGFloat = 0, right: CGFloat = 0) {
self = EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right)
}
init(horizontal: CGFloat = 0, vertical: CGFloat = 0) {
self = EdgeInsets(top: vertical, leading: horizontal, bottom: vertical, trailing: horizontal)
}
func with(left: CGFloat? = nil, right: CGFloat? = nil, top: CGFloat? = nil, bottom: CGFloat? = nil) -> UIEdgeInsets {
UIEdgeInsets(
top: top ?? self.top,
left: left ?? self.left,
bottom: bottom ?? self.bottom,
right: right ?? self.right
)
}
func with(horizontal: CGFloat, top: CGFloat? = nil, bottom: CGFloat? = nil) -> UIEdgeInsets {
UIEdgeInsets(
top: top ?? self.top,
left: horizontal,
bottom: bottom ?? self.bottom,
right: horizontal
)
}
func with(vertical: CGFloat, left: CGFloat? = nil, right: CGFloat? = nil) -> UIEdgeInsets {
UIEdgeInsets(
top: vertical,
left: left ?? self.left,
bottom: vertical,
right: right ?? self.right
)
}
}
InsetButton
- label과 button사이에 padding이 존재하는 버튼
- ButtonStyle 프로토콜을 준수하고, makeBoy안에서 label인스턴스에 접근하여 padding(EdgeInsets())로 inset값 설정
- 프로퍼티를 선언하면 사용하는쪽에서 초기화할때 주입이 가능
struct InsetButton: ButtonStyle {
var labelColor = Color.white
var backgroundColor = Color.blue
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(labelColor)
.padding(.init(horizontal: 20, vertical: 13))
.background(backgroundColor)
}
}
- 사용하는쪽
Button("InsetButton") {
print("tap button")
}
.buttonStyle(InsetButton(labelColor: .white, backgroundColor: .blue))
InsetRoundButton
- Inset이 존재하고 Round 모양의 버튼
- ButtonStyle을 준수하고, makeBody 안에서 뒤에서 배울 Capsule()을 사용하여 background 구현
struct InsetRoundButton: ButtonStyle {
var labelColor = Color.white
var backgroundColor = Color.blue
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(labelColor)
.padding(.init(horizontal: 20, vertical: 13))
.background(Capsule().fill(backgroundColor)) // <-
}
}
- 사용하는 쪽
Button("InsetRoundButton") {
print("tap button")
}
.buttonStyle(InsetRoundButton(labelColor: .white, backgroundColor: .blue))
InsetRoundScaleButton
- Inset이 존재하고 Round 모양이고 눌렀을때 누른 애니메이션을 주기 위해서 축소하는 모션이 있는 버튼
- scaleEffect 프로퍼티를 사용하여 애니메이션 효과 부여
struct InsetRoundScaleButton: ButtonStyle {
var labelColor = Color.white
var backgroundColor = Color.blue
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(labelColor)
.padding(.init(horizontal: 20, vertical: 13))
.background(Capsule().fill(backgroundColor))
.scaleEffect(configuration.isPressed ? 0.88 : 1.0) // <-
}
}
PrimitiveButtonStyle
- 더욱 상세하게 커스텀할 수 있는 프로토콜 (인터렉션, appearance 등)
- PrimitiveButtonStyle로 action의 트리거 타이밍도 직접 커스텀이 가능
ex) long press 조건 심기
* 3 distance만큼 벗어날 경우 long press 실패
* 2초 이상 누르고 있어야 long press로 인식
- PrimitiveButtonStyle 프로토콜을 준수하여 구현
- makeBody도 구현
struct MyPrimitiveButtonStyle: PrimitiveButtonStyle {
func makeBody(configuration: PrimitiveButtonStyle.Configuration) -> some View
}
}
- makeBody에 리턴할 MyBtton을 내부적으로 구현
- configuration.trigger()이것을 실행하는 타이밍에 action이 실행
func makeBody(configuration: PrimitiveButtonStyle.Configuration) -> some View {
MyButton(configuration: configuration, labelColor: labelColor, backgroundColor: backgroundColor)
}
struct MyButton: View {
@GestureState private var pressed = false
let configuration: PrimitiveButtonStyle.Configuration
var labelColor: Color
var backgroundColor: Color
var body: some View {
configuration.label
.foregroundColor(labelColor)
.padding(.init(horizontal: 20, vertical: 13))
.background(Capsule().fill(backgroundColor))
.scaleEffect(pressed ? 0.88 : 1.0)
.gesture(
// minimumDuration - 몇초 이상 지속되어야 longPress로 볼지 결정
// maximumDistance - 해당 거리보다 더 움직이면 longPress 실패로 결정
LongPressGesture(minimumDuration: 2, maximumDistance: 3.0)
.updating($pressed) { value, state, _ in state = value }
.onEnded { _ in self.configuration.trigger() }
)
}
* 참고
https://developer.apple.com/documentation/swiftui/primitivebuttonstyle
'iOS 기본 (SwiftUI)' 카테고리의 다른 글
[iOS - swiftUI] Toggle, ToggleStyle을 이용한 커스텀 토글 (0) | 2022.08.16 |
---|---|
[iOS - swiftUI] EditButton 사용 방법 (0) | 2022.08.09 |
[iOS - swiftUI] Button, ButtonStyle 사용 방법 (0) | 2022.08.05 |
[iOS - swiftUI] SF Symbols(San Francisco 심볼) 사용 방법 (0) | 2022.08.04 |
[iOS - swiftUI] Image, 원 이미지 사용 방법 (0) | 2022.08.03 |
Comments