Apple의 기본 UI는 뛰어나지만, 프로젝트에 따라 Alert의 커스텀을 요하는 경우가 있다.
본인은 주로 SwiftUI를 사용해서 개발을 하고 있는데, 보통 나라면 이런 경우 ZStack을 사용해서 Alert을 띄운다.
하지만 만약 하위 뷰에서 띄워야 한다면? 원하는 데로 동작을 안할 확률이 높다. 내가 원하는 것은 부모 뷰까지 꽉 차게 나오는 것이였기에 UIKit의 도움을 받아야 할 것 같았고 구현을 시도해보았다.
struct EndTicketAlert<Content>: View where Content: View{
let content: Content
let primaryButton: EndTicketAlertButton
let secondButton: EndTicketAlertButton?
init(content: () -> Content, primaryButton: () -> EndTicketAlertButton, secondButton: (() -> EndTicketAlertButton)? = nil){
self.content = content()
self.primaryButton = primaryButton()
self.secondButton = secondButton?()
}
var body: some View {
ZStack{
Color.black.opacity(0.3).ignoresSafeArea()
content
.padding(.bottom, 60)
.frame(maxWidth:315, minHeight: 169)
.overlay(
VStack(spacing:0){
if primaryButton.color == nil{
Divider()
}
HStack(){
primaryButton
if secondButton != nil{
Divider()
secondButton
}
}
.frame(height:60)
.font(.system(size: 16,weight: .bold))
}
, alignment: .bottom)
.background(
Color.white
).cornerRadius(10)
}
}
}
나의 프로젝트에서는 버튼 하나는 반드시 들어가고, 버튼은 최대 두개밖에 없었기에 이렇게 구현했다.
SwiftUI의 버튼은 Label의 길이만큼 터치가 가능해 이것 또한 살짝 커스텀을 할 필요가 있을 것 같아 구현했다.
struct EndTicketAlertButton: View {
typealias Action = () -> ()
private let action: Action
private let title: Text
let color: Color?
init(title:Text,color:Color? = nil,action: @escaping Action){
self.title = title
self.action = action
self.color = color
}
var body: some View {
Button{
action()
}label: {
title.frame(maxWidth:.infinity, maxHeight: .infinity)
}.background(color)
}
}
별 특별한 것은 아니고 label 길이를 무제한으로 준 것 뿐이다.
func alert<Content>(isPresented:Binding<Bool>, alert: () -> EndTicketAlert<Content>) -> some View where Content: View{
let keyWindow = UIApplication.shared.connectedScenes.filter ({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene}).compactMap {$0}.first?.windows.filter { $0.isKeyWindow }.first!
let vc = UIHostingController(rootView: alert())
vc.modalTransitionStyle = .crossDissolve
vc.modalPresentationStyle = .overCurrentContext
vc.view.backgroundColor = .clear
vc.definesPresentationContext = true
return self.onChange(of: isPresented.wrappedValue, perform: {
if $0{
keyWindow?.rootViewController?.topViewController().present(vc,animated: true)
}
else{
keyWindow?.rootViewController?.topViewController().dismiss(animated: true)
}
})
}
이제 이 alert을 어떤 뷰에서도 띄울 수 있게 하기 위해 extension을 이용했다. 최상위 ViewController를 찾아 alert을 띄우게 했고 Binding으로 Present가 되는 조건을 입력받아 ViewController로 같은 동작을 수행할 수 있도록 구현했다.
혹시 전체 소스를 볼 순 없을까요? 참고해서 적용해보려고하는데 부분소스만있어서 적용하기가 쉽지않네요 ㅠ