간단한 커스텀 바인딩으로 만든 코드
custom binding 내용을 알면 다음 코드가 가능하다는 것을 알 수 있습니다. 에러의 값이 바뀔 때 alert의 true, false로 변환하는 코드입니다.
extension Binding where Value == Bool {
init<T>(value: Binding<T?>) {
self.init(get: {
return value.wrappedValue != nil ? true : false
}, set: { newValue in
value.wrappedValue = nil
})
}
}
struct ContentView: View {
@State private var alert: MyCustomAlert? = nil
var body: some View {
Button("클릭") {
saveData()
}
.alert(alert?.title ?? "에러", isPresented: Binding(value: $alert), actions: {
alert?.getButtonAlert()
}) {
if let subtitle = alert?.subtitle {
Text(subtitle)
}
}
}
위에서 MyCustomAlert는 Error 타입의 enum 열거형이며 enum을 만들게 되면 사용되는 것들을 한번에 볼 수 있어서 편리해지기 때문에 유용하게 사용될 수 있습니다.
private func saveData() {
let isSuccessful: Bool = false
if isSuccessful {
} else {
alert = .noInternetConnection
}
}
버튼을 누르게 되면 .noInternetConnection으로 Error 값이 설정됩니다.
enum으로 에러 정의하기
enum에 다음처럼 정의하게 된다면 MyCustomAlert.dataNotFound.title 또는 MyCustomAlert.dataNotFound.subtitle 로 아래 String 데이터들을 접근할 수 있게 됩니다.
enum MyCustomAlert: Error, LocalizedError {
case noInternetConnection
case dataNotFound
case urlError(error: Error)
var title: String {
switch self {
case .noInternetConnection:
return "No Internet Connection"
case .dataNotFound:
return "No Data"
case .urlError:
return "Error"
}
}
var subtitle: String? {
switch self {
case .noInternetConnection:
return "Please check your internet connection and try again."
case .dataNotFound:
return nil
case .urlError(error: let error):
return "Error: \(error.localizedDescription)"
}
}
@ViewBuilder를 사용해 에러 종류의 따른 버튼 다르게 가져오기
엄청 유용한 기능으로 @ViewBuilder 와 함께 버튼을 만들게 된다면 에러에 따른 다른 버튼들을 사용할 수 있게 됩니다!
@ViewBuilder func getButtonAlert() -> some View {
switch self {
case .noInternetConnection:
Button("확인") {
}
case .dataNotFound:
Button("재시도") {
}
default:
Button("삭제") {
}
}
}
좀 더 깊게 파보면 만약 .noInternetConnection을 선택했을 때 @ViewBuilder로 뷰를 만들어 특정 동작을 취하게 하게 할 수도 있습니다. 다음 예시는 noInternetConnection 에서 onOkPressed 와 onRetryPressed 를 받도록 하였습니다.
enum MyCustomAlert: Error, LocalizedError {
case noInternetConnection(onOkPressed: () -> Void, onRetryPressed: () -> Void)
그리고 위헤서 받은 onOkPressed는 확인 버튼을 눌렀을 때 동작하는 코드를 나타내고 onRetryPressed 는 재시도 버튼을 눌렀을 때 동작하는 코드를 나타냅니다.
@ViewBuilder var getButtonAlert: some View {
switch self {
case .noInternetConnection(onOkPressed: let onOkPressed, onRetryPressed: let onRetryPressed):
Button("확인") {
onOkPressed()
}
Button("재시도") {
onRetryPressed()
}
아래처럼 사용될 수 있습니다.
alert = .noInternetConnection(onOkPressed: {
print("확인 버튼 클릭")
}, onRetryPressed: {
print("재시도 버튼 클릭")
})
View extension으로 클린 코드 만들기
다음으로는 코드를 좀 더 클린하게 만들기 위해서 View를 extension으로 showCustomAlert를 호출하면 사용자한테 알람을 보여주는 기존에 했던 코드를 한줄의 코드로 만들게끔 작성한 코드입니다.
wrappedValue는 Binding 값이 찾조하는 값입니다. 즉 Binding<T?>의 값으로 들어온 MyCustomAlert의 값을 의미합니다.
extension View {
func showCustomAlert<T: AppAlert>(alert: Binding<T?>) -> some View {
self
.alert(alert.wrappedValue?.title ?? "에러", isPresented: Binding(value: alert), actions: {
alert.wrappedValue?.buttons
}) {
if let subtitle = alert.wrappedValue?.subtitle {
Text(subtitle)
}
}
}
}
위처럼 구현하게 되면 아래 코드처럼 한출의 코드로 바뀌게 됩니다.
var body: some View {
Button("클릭") {
saveData()
}
.showCustomAlert(alert: $alert)
}
위에서 AppAlert 프로토콜을 준수하는 제너릭 T이 들어가 있는데 이것은 알람에서 주로 사용되는데 MyCustomAlert에서도 들어가는 요소들을 포함하는 프로토콜이며 코드를 좀 더 유연하게 만들어줍니다.
protocol AppAlert {
var title: String { get }
var subtitle: String { get }
var buttons: AnyView { get }
}
전체 코드
import SwiftUI
protocol AppAlert {
var title: String { get }
var subtitle: String { get }
var buttons: AnyView { get }
}
extension Binding where Value == Bool {
init<T>(value: Binding<T?>) {
self.init(get: {
return value.wrappedValue != nil ? true : false
}, set: { newValue in
value.wrappedValue = nil
})
}
}
extension View {
func showCustomAlert<T: AppAlert>(alert: Binding<T?>) -> some View {
self
.alert(alert.wrappedValue?.title ?? "에러", isPresented: Binding(value: alert), actions: {
alert.wrappedValue?.buttons
}) {
if let subtitle = alert.wrappedValue?.subtitle {
Text(subtitle)
}
}
}
}
struct ContentView: View {
@State private var alert: MyCustomAlert? = nil
var body: some View {
Button("클릭") {
saveData()
}
.showCustomAlert(alert: $alert)
}
// enum MyCustomError: Error, LocalizedError {
// case noInternetConnection
// case dataNotFound
// case urlError(error: Error)
//
// var errorDescription: String? {
// switch self {
// case .noInternetConnection:
// return "Please check your internet connection and try again."
// case .dataNotFound:
// return "There was an error loading data. Please try again."
// case .urlError(error: let error):
// return "Error: \(error.localizedDescription)"
// }
// }
// }
enum MyCustomAlert: Error, LocalizedError, AppAlert {
case noInternetConnection(onOkPressed: () -> Void, onRetryPressed: () -> Void)
case dataNotFound
case urlError(error: Error)
var errorDescription: String? {
switch self {
case .noInternetConnection:
return "Please check your internet connection and try again."
case .dataNotFound:
return "There was an error loading data. Please try again."
case .urlError(error: let error):
return "Error: \(error.localizedDescription)"
}
}
var title: String {
switch self {
case .noInternetConnection:
return "No Internet Connection"
case .dataNotFound:
return "No Data"
case .urlError:
return "Error"
}
}
var subtitle: String {
switch self {
case .noInternetConnection:
return "Please check your internet connection and try again."
case .dataNotFound:
return "Data Not Found check again"
case .urlError(error: let error):
return "Error: \(error.localizedDescription)"
}
}
var buttons: AnyView {
AnyView(getButtonAlert)
}
@ViewBuilder var getButtonAlert: some View {
switch self {
case .noInternetConnection(onOkPressed: let onOkPressed, onRetryPressed: let onRetryPressed):
Button("확인") {
onOkPressed()
}
Button("재시도") {
onRetryPressed()
}
case .dataNotFound:
Button("재시도") {
}
default:
Button("삭제") {
}
}
}
}
private func saveData() {
let isSuccessful: Bool = false
if isSuccessful {
} else {
alert = .noInternetConnection(onOkPressed: {
print("확인 버튼 클릭")
}, onRetryPressed: {
print("재시도 버튼 클릭")
})
}
}
}
#Preview {
ContentView()
}
'SwiftUI' 카테고리의 다른 글
Chat GPT API 사용하기 (3) | 2024.05.15 |
---|---|
공공 데이터 지도에 띄우기 (with 네이버 지도) (0) | 2024.05.13 |
custom binding (0) | 2024.05.10 |
지도에서 내 현재 위치 가져오기 (네이버 지도) (0) | 2024.05.09 |
카메라를 이용하여 QR code 스캔하기 (0) | 2024.05.07 |