반응형
안드로이드에서는 기본적으로 있지만 swift에서는 직접 구현해주어야합니다.
toast 관련해서 라이브러리도 존재하고 swiftUI ViewModifier로 구현한 예제도 많습니다.
하지만 안드로이드랑 다르게 현재 자신의 뷰에서만 표시되고 이전 뷰로 가거나 다음 뷰로 이동 시, 토스트가 사라지거나 가려지는 문제가 있었습니다.
그래서 연구하는 도중 WindowScene 맨 위로 올리면 어떻게 될까 구현해본 결과, 항상 맨위에 존재하는 것을 확인할 수 있었습니다.
위의 이미지와 같이 뷰가 이전으로 가거나 다시 생기더라도 항상 Toast가 맨 위에 존재하는 것을 확인할 수 있습니다.
1. Get WindowScenes
func getWindowScenes() -> [UIWindow] {
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
}
해당 코드는 windowScene를 Get하는 코드입니다. UIApplication.shared.windows이 deprecated가 났기 때문에 저는 따로 코드를 분리하여 얻어오도록 하였습니다.
2. Show & Dismiss
// 토스트 화면 View
var toastView: UIView?
...
// 토스트 화면 Show
func showToastView(message: String) {
let window = getWindowScenes().last
toastView = UIHostingController(rootView: ToastView(message: message,
edge: window?.safeAreaInsets))).view
guard let toastView = toastView else {
return
}
window?.addSubview(toastView)
}
// 토스트 화면 Dismiss
func dismissToastView() {
toastView?.removeFromSuperview()
toastView = nil
}
windowScene의 last에 toastView를 추가해 줍니다. 이후 모두 사용해주었다면 removeFromSuperview로 제거해줍니다.
edge를 매개변수에 추가한 이유는 ToastView를 SwiftUI로 제작하는데 Geometry safearea가 잘 안나오고 있었습니다.
확실하지 않지만, 아마 새 window에 ToastView만 따로 추가해서 그런지 부모 View가 없어서 감지를 못하는 것 같았습니다.
그래서 window의 edge를 같이 변수에 추가하였습니다.
3. ToastView Example
import SwiftUI
struct ToastView: View {
@State var isShowing: Bool = true
@State var width: CGFloat = 0
@State var height: CGFloat = 0
var message: String = ""
var safeArea: CGFloat = 0
init(message: String, edge: UIEdgeInsets?) {
self.message = message
self.safeArea = max(max(edge.top, edge.bottom), max(edge.left, edge.right))
self._width = State(initialValue: getX(orientation: [ 현 디바이스 회전 상태 ]))
self._height = State(initialValue: UIScreen.main.bounds.size.height * 0.8)
}
var body: some View {
GeometryReader { _ in
if isShowing == true {
VStack {
Color.black.opacity([ 원하는 value ])
.frame(width: [ 원하는 크기 ], height: [ 원하는 크기 ])
.offset(x: width, y: height)
// 원하는 View 속성
.viewOverlay(alignment: .center, content: {
Text(message)
// 원하는 Text 속성
.multilineTextAlignment(.center)
.offset(x: width, y: height)
})
}.onRotate { newOrientation in
if HardwareInfo.isPadDevice() == false {
height = UIScreen.main.bounds.size.height * 0.8
width = getX(orientation: newOrientation)
} else {
width = UIScreen.main.bounds.size.height / 2 - [ 원하는 크기의 절반 ]
height = UIScreen.main.bounds.size.width * 0.8
}
}
}
}.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + [ 원하는 시간 ]) {
self.isShowing = false
dismissToastView()
}
}
}
fileprivate func getX(orientation: UIDeviceOrientation) -> CGFloat {
if orientation == .portrait {
return (UIScreen.main.bounds.size.width / 2) - [ 원하는 크기의 절반 ]
} else {
return (UIScreen.main.bounds.size.width / 2) - ([ 원하는 크기의 절반 ] + safeArea)
}
}
}
이는 제가 예제로 제작한 ToastView 코드입니다. 몇가지 제거한 부분이 있는데 해당 내용은 그냥 이러한 형태로 구현해봤다 라는 느낌만.. 알아주셨으면 합니다 ㅎㅎ
토스트 뷰가 나올 때 다른 뷰로 이동하거나 현재 뷰를 없애는 경우가 없다면 라이브러리를 사용해도 되지만.. 만약 저처럼 여러 뷰를 옮겨야 하는 상황이라면 고려해보시면 좋을 거 같습니다.
반응형
'Develop > Swift' 카테고리의 다른 글
[iOS] Xcode Code Snippet (0) | 2022.05.08 |
---|---|
[Objective-C] message, location으로 매개변수 이름정하면 에러.. (0) | 2022.05.07 |
[SwiftUI] TabView PageStyle 회전 연구 (iPad 기준) (0) | 2022.05.03 |
[SwiftUI] ViewModifier 사용해보기 (0) | 2022.05.03 |
[SwiftUI] fullScreenCover 고찰 (0) | 2022.05.02 |