기존 UIKit 앱을 SwiftUI로 ~ Bes Practice

JaeEun Lee·2024년 11월 3일

SwiftUI & Jetpack compose

목록 보기
6/10

SwiftUI는 UIKit과 함께 사용할 수 있으며 SwiftUI로 앱 전체를 한 번에 재작성하는 대신 점진적으로 전환하는 것이 현실적이고 효율적입니다. UIKit의 기존 ViewController와 SwiftUI의 뷰를 통합할 수 있으므로 필요한 부분부터 점진적으로 SwiftUI로 전환하는 방법에 대해서 정리해 보았습니다.

UIHostingController 사용

UIKit의 ViewController에서 SwiftUI 뷰를 사용할 수 있도록 UIHostingController를 제공합니다.

  let swiftUIView = ContentView()
let hostingController = UIHostingController(rootView: swiftUIView)
present(hostingController, animated: true)

UIViewControllerRepresentable 사용

반대로 SwiftUI에서 기존 UIKit의 UIViewController나 UIView를 포함할 수 있습니다. 이를 통해 SwiftUI와 UIKit 간의 상호 운용성을 유지하면서도 SwiftUI를 점진적으로 도입할 수 있습니다.

UIKit의 컴포넌트를 UIViewControllerRepresentable를 상속받아 선언형 컴포넌트로 변경시켜주는 형태입니다.

struct MyUIKitViewController: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> SomeUIKitViewController {
        return SomeUIKitViewController()
    }
    
    func updateUIViewController(_ uiViewController: SomeUIKitViewController, context: Context) {
        // 업데이트 로직
    }
}

데이터와 상태 관리 방식 일관화

기존 앱에서 사용하는 데이터 모델과 ViewModel은 SwiftUI와 호환되도록 설계합니다. SwiftUI에서는 단방향 데이터 흐름을 유지하므로 기존 데이터 모델이 이 흐름을 따르도록 수정해야 할 수도 있습니다.

ObservableObject와 @Published 활용

기존 데이터 모델을 SwiftUI와 호환되도록 ObservableObject와 @Published 속성으로 변환합니다. 데이터 변경이 UI에 반영되도록 SwiftUI의 반응형 상태 관리를 도입합니다.

class MyViewModel: ObservableObject {
    @Published var data = "Initial Data"
}

상태 호이스팅 도입

SwiftUI에서는 상태가 하나의 출처에서 관리되고 이를 자식 뷰로 전달하는 방식이 일반적이므로, 기존의 복잡한 상태 관리 로직을 단순화할 수 있습니다.

기존 UIKit과 SwiftUI의 생명 주기 차이 이해

UIKit과 SwiftUI는 서로 다른 생명 주기 이벤트를 가지고 있습니다. SwiftUI는 onAppear, onDisappear와 같은 생명 주기 메서드를 사용하며, UIKit에서는 viewDidLoad, viewWillAppear 등이 사용됩니다. SwiftUI로 전환할 때는 이 차이를 고려하여 상태 초기화와 클린업을 구현해야 합니다.

onAppear와 onDisappear 활용

SwiftUI에서는 onAppear에서 데이터 초기화, onDisappear에서 클린업 작업을 처리할 수 있습니다.

struct ContentView: View {
    var body: some View {
        Text("Hello, SwiftUI!")
            .onAppear {
                // 초기화 작업
            }
            .onDisappear {
                // 클린업 작업
            }
    }
}

기존 뷰 컨트롤러 기반 상태 관리와 SwiftUI의 상태 래퍼 통합

UIKit에서는 UIViewController가 상태를 관리하던 방식에서 SwiftUI로 전환할 때는 @StateObject와 @ObservedObject로 상태를 관리하게 됩니다.

ViewModel을 ViewController에서 SwiftUI View로 주입
기존 앱의 ViewModel이나 데이터를 SwiftUI View에 전달할 때 @ObservedObject나 @EnvironmentObject를 사용하여 SwiftUI와 일관된 상태 관리를 유지할 수 있습니다.

class MyViewController: UIViewController {
    private let viewModel = MyViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let swiftUIView = ContentView(viewModel: viewModel)
        let hostingController = UIHostingController(rootView: swiftUIView)
        
        addChild(hostingController)
        view.addSubview(hostingController.view)
        hostingController.view.frame = view.bounds
        hostingController.didMove(toParent: self)
    }
}

코드 리팩토링 ~ 뷰와 비즈니스 로직 분리

SwiftUI는 상태 관리와 UI 구성이 분리되어야 최상의 성능을 발휘합니다. 기존 UIKit 코드에서는 UI와 로직이 혼합되어 있을 가능성이 있으므로, SwiftUI로 전환하면서 ViewModel을 통해 비즈니스 로직을 분리하는 리팩토링을 진행합니다.

MVVM 패턴 적용

뷰 로직은 ViewModel에 포함하고, SwiftUI 뷰에서는 데이터 표시와 사용자 입력만 처리합니다. 이 패턴을 통해 SwiftUI의 반응형 데이터 바인딩을 쉽게 적용할 수 있습니다.

미리보기 기능을 활용한 빠른 UI 확인

SwiftUI는 PreviewProvider를 통해 다양한 기기에서 UI를 빠르게 확인할 수 있는 미리보기 기능을 제공합니다. 기존 UIKit에서 시뮬레이터와 기기 테스트에 많은 시간이 걸렸다면, SwiftUI 미리보기 기능을 적극 활용하여 레이아웃과 UI 상태를 빠르게 검토할 수 있습니다.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(viewModel: MyViewModel())
            .previewDevice("iPhone 13")
    }
}

컴포넌트화하여 재사용성 향상

SwiftUI로 전환할 때, 중복된 UIKit UI 요소들을 SwiftUI에서 재사용 가능한 컴포넌트로 컴포넌트화하여 전환하면 향후 유지보수가 훨씬 수월해집니다. 작은 UI 요소들을 독립적인 컴포넌트로 만들어 코드의 재사용성과 가독성을 높일 수 있습니다.

struct CustomButton: View {
    let title: String
    let action: () -> Void
    
    var body: some View {
        Button(action: action) {
            Text(title)
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(8)
        }
    }
}
profile
공업철학프로그래머

0개의 댓글