-
전역적인 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 프로퍼티를 통해 숨겨줄지 보여줄지 결정해주게 바인딩 시켜줍니다.
그럼 구동 고고🏃🏻
위와 같이 기존 메인 뷰 위에 조건을 달성하면 로딩 윈도우가 올라가서 나타나는것을 볼 수 있습니다.
마무리
뷰를 컴포저블하게 가져갈 시 전역적으로 구현되어야할 로딩, 토스트, 바텀시트 등 되게 다양한 뷰의 구현이 필요한데 이럴때마다 사이즈를 분기 시켜 처리하거나 아니면 최상단으로 끌어들여가 의존성을 가지고 구현하기는 여간 귀찮고 유지보수가 힘든게 아니더라구요🥲
그럴때 이 방법도 하나의 방법이 될 수 있는것 같습니다🙌
'SwiftUI' 카테고리의 다른 글
SwiftUI - antialiased & interpolation (0) 2022.12.06 SwiftUI - renderingMode (0) 2022.12.02 SwiftUI - LazyVGrid & LazyHGrid (0) 2022.11.24 SwiftUI - PinnedScrollableViews (a.k.a Sticky View) (0) 2022.11.21 SwiftUI - Infinity Carousel View (feat. TCA) (1) 2022.11.17