SwiftUI/MVVM 알아보기

lottie-ios 프레임워크와 UIViewRepresentable 을 이용한 LoadingView 구현

basker 2020. 8. 27. 20:44

로띠(lottie) 는 json 형식의 움직이는 이미지 포맷으로 aribnb에서 개발되었고 많은 웹이나 앱에서 사용되고 있다. UIKit과 SwiftUI에서는 직접 이를 지원하지 않으므로 lottie-ios 프레임워크를 Pod을 통해 설치하고 이를 통해서 LottieView를 구현할 수 있다. 

SwiftUI는 지원하지 않는 UIKit의 WebView 나 MapView등 사용하기 위해 UIViewRepresentable 프로토콜을 정의하고 있고 이를 구현함으로 써 쉽게 이를 SwiftUI로 전환 할 수 있다. 여기서는 lottie-ios 프레임워크와 UIViewRepresentable 프로토콜 구현을 통해 LottieView를 구현하고 이를 통해 날씨 정보를 로딩 하는 중 이를 화면에 표시하는 LodingView를 구현하는 것으로 날씨 앱 구현을 마무리한다. 

 

lottie-ios는 terminal 에서 아래와 같이 설치할 수 있다. 이미 Cocoa Pods 이 설치되어 있으면 첫번째 과정은 수행하지 않는다.  

Cocoa Pods 설치 

$ sudo gem install cocoapods 
$ pod setup —verbose

Cocoa Pods 에서 lottie-ios 설치 

현재 Project 폴더로 이동한 후 현재 프로젝트에서 아래와 같이 수행 
$ pod init
$ open Podfile

Podfile에 pod 'lottie-ios' 추가 

Podfile 내용 

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'ClimaForSwiftUI' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for ClimaForSwiftUI
  pod 'lottie-ios'

  target 'ClimaForSwiftUITests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'ClimaForSwiftUIUITests' do
    # Pods for testing
  end

end

수정 완료후 프레임워크 설치를 위해 아래와 같이 입력

$ pod install
$ open .


이후 프로젝트파일(.xcodeproj) 파일이 아닌 워크스페이스(.wcworkspace) 파일을 클릭해 XCode를 재 실행한다. 

LottieView 구현

LottieView는 lottie-ios 프레임워크의 AnimationView를 생성하고 이를 UIView의 하위뷰로 설정한 후 이를 반환하는 팩토리 함수와 해당

뷰를 업데이트하는 함수를 가지는 UIViewRepresentable 프로토콜을 구현해야 한다. 이를 구현한 LottieView는 아래와 같다.  

LottieView.swift

import SwiftUI
import Lottie

struct LottieView: UIViewRepresentable {
    var filename: String
    //UIViewRepresentable 프로토콜의 UIView 팩토리 메서드 구현
    func makeUIView(context: Context) -> UIView {
        let view = UIView(frame: .zero)
        //로디 애니메이션 뷰 생성
        let animationView = AnimationView()
        //애니메이션 파일 설정
        animationView.animation = Animation.named(filename)
        //에니메이션 스케일 설정
        animationView.contentMode = .scaleAspectFit
        //애니메이션 시작
        animationView.play()
        animationView.translatesAutoresizingMaskIntoConstraints = false
        //해당 에니메이션 뷰를 생성할 View의 하위뷰로 설정
        view.addSubview(animationView)
        
        //상위뷰의 크기로 애니메이션 뷰의 크기 설정
        NSLayoutConstraint.activate([
            animationView.widthAnchor.constraint(equalTo: view.widthAnchor),
            animationView.heightAnchor.constraint(equalTo: view.heightAnchor)
                                                 
        ])
        return view
    }
    //UIViewRepresentable 프로토콜의 UIView 업데이트 메서드 구현
    func updateUIView(_ uiView: UIView, context: Context) {
      
    }
}


 

이제 LottieView를 통해 LodingView를 아래와 같이 구현한다. 

LoadingView.swift

import SwiftUI

struct LoadingView: View {
    var body: some View {
        VStack(alignment: .center) {
            Spacer()
            LottieView(filename: "data")
            Text("Loading...").font(.title)
                .foregroundColor(.gray)
            Spacer()
        }.frame(width: 200, height: 200)
        .background(Color(red: 0.965, green: 0.969, blue: 0.972))
        .cornerRadius(20)
            .shadow(color: /*@START_MENU_TOKEN@*/.black/*@END_MENU_TOKEN@*/, radius: /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/, x: 20, y: 20)
    }
}

struct LoadingView_Previews: PreviewProvider {
    static var previews: some View {
        LoadingView()
    }
}

이제 WeatherViewModel은 날씨 정보를 가져올 때 로딩 뷰를 화면에 보여주고 완료시 로딩뷰를 화면에서 없애고 날씨 정보가 보이도록 isLoading 플래그를 추가하고 private func load(resource:) 함수 시작과 종료시 플래그를 변경하도록 한다. 

 

WeatherViewModel.swift

class WeatherViewModel : ObservableObject {
    ...
    @Published var isLoading : Bool = false
}

extension WeatherViewModel {
    ... 
    private func load(resource: Resource<WeatherData>) {
        isLoading = true
        URLRequest.load(resource: resource) { (result) in
            switch result {
            case .success(let weatherData) :
                self.model = weatherData
            case .failure(let error) :
                print(error)
            }
            self.isLoading = false
        }
    }
}

이제 해당 뷰 모델을 관찰하고 있는 뷰는 isLoading 의 변경을 통지 받을 수 있고 이를 통해서 true일 때 로딩화면을 표시하고 false일 때 로딩화면을 제거할 수 있게 된다. 

ContentView.swift

struct ContentView: View {
    ...  
    var body: some View {
        ZStack {
            ...
            if viewModel.isLoading {
                LoadingView()
            }
        }
    }
}

지금 까지 SwitUI를 이용해 날씨 앱을 구현한 전체 프로젝트는 아래에 추가한다.

 

ClimaForSwiftUI.tar.gz
0.22MB