ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 전역적인 Window 객체를 이용해 LoadingView 띄우기
    SwiftUI 2022. 11. 29. 16:40

    안녕하세요. 그린입니다🍏

    이번 포스팅에서는 전역적으로 아예 뷰를 감싸버려 로딩뷰를 윈도우로 만들어 띄우는 방법을 포스팅 해보겠습니다!

     

    왜 이런 구현이 필요하게 되었나요?

    우선 각 피쳐에서 뷰를 구성함에 있어 바텀 영역이 존재할때도 있고 없을때도 있습니다.

    즉, 각 다른 구성의 뷰에서 해당 로딩뷰를 종속으로 띄워준다면 보여지는 뷰의 패딩이 달라지기에 보여지는 위치가 달라질 수 있습니다.

    이를 해결하기 위해 기존 뷰의 조합들이 이뤄진 윈도우 위에 새로운 윈도우를 만들어 덮어버리는 방식도 방법일것 같아 구현해봤어요!

    이 구현을 위해서는 우선 띄워줄 로딩 뷰 자체를 만들어야 합니다.

    그 다음으로는 로딩뷰를 담을 새롭게 공유할 윈도우 객체를 만들고 초기화 시 UIHostingController를 이용해 해당 로딩뷰를 지정해줍니다.

    마지막으로 SwiftUI에서 각 뷰에서 조금 더 간편하게 사용할 수 있도록 View Modifier를 만들어주고 사용하면 끄으으으읕!!🔚

     

    그럼 바로 구현해보도록 하죠 🙌

    구현 START⭐️

    로딩 뷰 부터 아주 간단히 구현해보죠!

    import SwiftUI
    
    public struct LoadingView: View {
      public init() { }
      
      public var body: some View {
        VStack(spacing: 10) {
          ProgressView()
          Text("Loading")
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(DesignSystem.Colors.white100.opacity(0.5))
      }
    }

    SwiftUI의 기본 API인 ProgressView를 가져옵니다.

    이 뷰는 프로그래스바를 도는 그런 뷰에요!

    그 다음 프레임을 무한정으로 잡아주고 배경색은 로딩뷰이니만큼 불투명하게 0.5를 주어 해당 로딩이 되는 사이 배경은 딤드 처리 되어 보일 수 있도록 해줍니다.

     

    그 다음으로 해당 로딩 뷰를 얹어줄 Window를 만들어주겠습니다~

    import SwiftUI
    import UIKit
    
    public class LoadingWindow: UIWindow {
      public static let shared = LoadingWindow(frame: UIScreen.main.bounds)
    
      required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented: please use LoadingWindow.shared")
      }
      
      override init(frame: CGRect) {
        super.init(frame: frame)
        let loadingViewController = UIHostingController(rootView: LoadingView())
        loadingViewController.view?.backgroundColor = .clear
        self.rootViewController = loadingViewController
        self.isHidden = true
      }
    
      func show() {
        self.isHidden = false
      }
    
      func hide() {
        self.isHidden = true
      }
    
      func toggle() {
        if self.isHidden {
          show()
        } else {
          hide()
        }
      }
    }

    전체에서 사용될 것으로 싱글턴으로 해당 윈도우 class 객체를 만들어줍니다.

    초기화 시 UIHostingController를 통해 rootView를 위에서 만들어준 LoadingView로 지정해줘요.

    그 다음 우선 숨겨놓기 위해 isHidden 처리해줍니다.

    해당 객체에서 메서드는 해당 로딩뷰를 보여주고 숨겨주는 처리를 위한 각각의 역할을 담은 메서드를 구현해줘요.

     

    자 이제 ViewModifier를 만들어볼께요!

    import SwiftUI
    
    public extension View {
      func loading(
        _ isLoading: Bool
      ) -> Self {
        if isLoading {
          LoadingWindow.shared.show()
        } else {
          LoadingWindow.shared.hide()
        }
        return self
      }
    }

    어떠한 뷰에서든 이 loading 메서드를 통해 위에서 만들어준 공유된 LoadingWindow를 보여주고 숨겨줄 수 있습니다.

     

    그럼 마지막으로 한가지 빼먹은게 있어요ㅎㅎ

    바로 LoadingWindow를 앱 실행 시 등록하는거에요🙋🏻

    그걸 위해 앱 컨트롤을 해주는 AppDelegate로 가줍니다.

    @main
    class AppDelegate: UIResponder, UIApplicationDelegate {
      var window: UIWindow?
    
      func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        
        // 메인 뷰
        
        window?.rootViewController = UIHostingController(rootView: 메인 뷰)
        window?.makeKeyAndVisible()
        
        // 로딩윈도우
        setupLoadingWindow()
        return true
      }
      
      func setupLoadingWindow() {
        _ = LoadingWindow.shared
      }

    해당 구현을 통해 앱이 실행되었을때 로딩 윈도우를 추가로 등록해줍니다.

     

    이제 지인짜아 다끝!!!!!

    이제 적용해보겠습니다.

    public struct MainView: View {
      private var store: Store<MainState, MainAction>
      
      public var body: some View {
        WithViewStore(store) { viewStore in
        // 메인 뷰 구현
          .loading(viewStore.state.isLoading)
        ...
        }
      }
    }

    위에서 만든 View Modifier를 적용해서 원하는 뷰에서 구현해줍니다.

    저는 TCA를 기반으로 사용하여서 viewStore State의 isLoading Bool 프로퍼티를 통해 숨겨줄지 보여줄지 결정해주게 바인딩 시켜줍니다.

     

    그럼 구동 고고🏃🏻

    위와 같이 기존 메인 뷰 위에 조건을 달성하면 로딩 윈도우가 올라가서 나타나는것을 볼 수 있습니다.

     

    마무리

    뷰를 컴포저블하게 가져갈 시 전역적으로 구현되어야할 로딩, 토스트, 바텀시트 등 되게 다양한 뷰의 구현이 필요한데 이럴때마다 사이즈를 분기 시켜 처리하거나 아니면 최상단으로 끌어들여가 의존성을 가지고 구현하기는 여간 귀찮고 유지보수가 힘든게 아니더라구요🥲

    그럴때 이 방법도 하나의 방법이 될 수 있는것 같습니다🙌

Designed by Tistory.