UIWindow와 Toast

박성민·5일 전
0

iOS

목록 보기
31/31

최근에 조금 시간이 나서 이전부터 개선 하고 싶었던 Toast 다시 개발하게 되었습니다.

개선하고 싶었던 것은

  • view가 아닌 window에 띄워 다른 UI나 작업들에 영향을 받지 않고 최상단에 띄우기.
  • Queue를 사용해서 여러개의 Toast를 순차적으로 띄우기
    입니다.

그럼 먼저 UIWindow에 대해 살짝 알아보죠.

UIWindow

개요

목적

The backdrop for your app’s user interface and the object that dispatches events to your views
설명하자면 UIWindow는 UIView의 서브 클래스로, 앱의 UI를 담는 컨테이너이며, view에 이벤트를 전달하는 객체입니다. UIWindow는 뷰 컨트롤러와 함께 이벤트를 처리하고 앱의 기본적인 작업을 수행합니다. UIKit이 대부분의 창 관련 상호작용을 처리합니다

필요한 경우

  • 앱의 콘텐츠를 표시할 main window 제공.
  • 추가 콘텐츠를 표시하기 위한 추가 window 생성.

추가 기능

  • 창의 z축 레벨 설정 (다른 윈도우에 대한 가시성에 영향을 미침).
  • 윈도우를 표시하고 키보드 이벤트의 대상이 되도록 설정.
  • 좌표 값을 윈도우의 좌표 시스템으로 변환.
  • 루트 뷰 컨트롤러 변경.
  • 윈도우가 표시되는 화면 변경.

기타

UIWindow 자체는 시각적인 화면이 없으며, 하나 이상의 View를 호스팅합니다. 이 view는 window의 rootViewController에 의해 관리됩니다.

왜? 별도 Window인가?

Window를 통해 z축을 조절할 수 있다는 점 + 인앱 메시지를 보낼 수 있는 라이브러리에서도 Window를 새로 만들어서 사용하는 것을 보고 Toast용 Window를 만들면 어떨까 생각했습니다.

// sceneDelegate에서 windowScene을 받아서 사용
 let window = UIWindow(windowScene: windowScene)

 window.windowLevel = .alert // 최상단으로 사용. 기본은 normal
 window.backgroudColor = .clear
 window.isUserInteractionEnabled = false
 window.isHidden = true

이렇게 만든 window를 Toast를 띄우는데 사용했습니다.

기존 Toast

이전 Toast는 해당 ViewController View의 최상단에 띄웠었습니다.

여기서 발생하는 문제는 새로운 viewController가 push 되거나 화면 이동이 발생하면 Toast가 가려지게 된다는 점입니다.
keyWindow를 찾아 최상단에 Toast를 띄워준다 하더라도 위와 같이 modal을 새롭게 present하게 되면, 뷰 계층 순서가 바뀌어서 가려지게 됩니다.

신규 Toast


이렇게 별도로 만든 Toast용 Window는 main Window보다 z축 레벨이 더 높기 때문에 영향을 받지 않는 다는 것을 확인할 수 있습니다.

Serial Queue

Serial Queue의 들어온 작업은 하나의 작업이 끝나야 다음 작업을 시작합니다.

  ...생략...
    private let serialQueue = DispatchQueue(label: "toaster.queue")
    private var toastQueue: [Toast] = []
    private var isShowing: Bool = false
    
    func showToast(_ toast: Toast) {
    	serialQueue.async { [weak self] in
            self?.toastQueue.append(toast)
            self?.showNextToast()
        }
    }
    
    private func showNextToast() {
        serialQueue.async { [weak self] in
            guard let self,
                  !self.isShowing,
                  !self.toastQueue.isEmpty,
                  let window else { return }
            
            self.isShowing = true
            
            DispatchQueue.main.async {
                let toast = self.toastQueue.removeFirst()
                self.toastView.text = toast
               
                UIView.animate(withDuration: 0.3, delay: .zero, options: .curveEaseIn, animations: {
                    window.alpha = 1
                }) { _ in
                    UIView.animate(withDuration: 0.3, delay: 2, animations: {
                        window.alpha = 0
                    }) { _ in
                        self.serialQueue.async {
                            self.isShowing = false
                            self.showNextToast()
                        }
                    }
                }
            }
        }
    }
    

showToast를 통해 Toast를 Queue에 넣어주고 Serial Queue를 사용해 하나씩 순서대로 보여지도록 설정하였습니다.

참고자료

profile
iOS시작~

0개의 댓글