iOS/SwiftUI

#4 Integrating SwiftUI

HaningYa 2020. 10. 20. 01:25
728x90

SwiftUI 는 너무 씽나서 앱 전체에 사용하는 걸 참을쑤가 없써!!

하지만 많은 앱들은 이미 UIKit으로 개발되어 있어서 코드 전체를 SwiftUI로 다시 짤 수 는 없는데 어떻게 하면 좋을까?

당연히 Apple이 그 생각도 했지.

이미 개발된 UIKit 앱에 SwiftUI view를 추가하는 것을 "super-easy" 하게 만들어 놨다.

조금 코드를 더 작성하면 SwiftUI view 와 데이터를 교환하는 UIKit view 도 만들수 있다.

이런 부분을 통해 SwiftUI Control의 단점을 극복할 수 도 있을 것 이다.

*hosting 이라는 용어는 UIKit 앱이 SwiftUI view 를 host 할 수 있어서 붙여진 용어이다. (반대로도 가능)

이번장에서 배울 것은

  • Host SwiftUI view in UIKit (UIKit 안에 SwiftUI 넣기)
  • Host view controller in SwiftUI Project (SwiftUI 에 viewcontroller 넣기)
  • Host UIKit view with data dependencies in SwiftUI Project (데이터 의존성이 있는 UIKit View를 SwiftUI 프로젝트에 넣기)

*SwiftUI 를 사용하기 위해서는 deployment target 을 iOS13 이상으로 해야한다.


UIKit 프로젝트에 SwiftUI view 호스팅하기

  1. SwiftUI view 파일을 프로젝트에 추가한다.
  2. SwiftUI view present 를 위한 버튼을 추가한다.
  3. Hosting Controller 를 Storyboard 로 drag 하여 segue 를 만든다.
  4. segue를 view controller code에 있는 @IBSegueAction에 연결하고 hosting controller 의 rootView를 너의 SwiftUI view의 instance로 설정한다.

@IBSegueAction 은 Xcode11 에서 새로나온 기능이다. 

UIKit앱을 prepare(for:sender:) 대신에 사용할 수 있다.

특히 destination view controller를 생성할때 프로퍼티를 세팅하고 싶을때 유용하다.

segue 에 직접적으로 연결되기 때문에 segue 식별자도 필요하지 않다.


SwiftUI 프로젝트에 view controller hosting 하기

  1. ViewController.swift 와 Storyboard 를 SwiftUI 프로젝트에 추가한다.
  2. storyboard의 identity inspector 에 Storyboard ID를 설정한다.
  3. ViewController 의 표현을 위한 구조체를 만든다.
  4. ContentView에 NavigationLink를 달아준다.

* 표현을 위한 구조체는 해당 view controller 의 class 외부에 있어야한다. 구조체 이름은 view controller 와 일치하지 않아도 되지만 withIdentifier 파라미터는 Storyboard ID 와 동일해야 한다.

UIViewControllerRepresentable 프로토콜은

make와 update 메소드를 필요로 한다. makeUIViewController(context:)의 경우 ViewController 를 지정된 Storyboard에서 인스턴스화 시키켜준다. (그래서 storyboard ID 가 필요하다)

updateUIViewController(_:context:)는 비운체로 놔둘 것이다. 왜냐하면 현재 ViewController 는 SwiftUI view 의 데이터를 사용하지 않기 때문이다. 

SwiftUI view 가 ViewController 쪽의 데이터를 사용하지 않기 때문에 ViewControllerRepresentation은 필요가 없지만 구현해놓는게 좋다. 

Navigating to the view Controller

1. 화면이동을 위한 버튼을 만들어 준다.

            NavigationLink(destination: ViewControllerRepresentation()) {
                Text("Play BullsEye")
            }

2. 이동에 필요한 정보를 세팅해준다.

  • 최상단 계층의 VStack을 NavigationView로 감싸주면서 NavigationLink 가 동작하게 만든다.
  • 최상단 VStack의 navigation bar title을 설정한다.
  • NavigationView에게 default split-view navigation view style를 적용해준다.

*이동된 viewcontroller 의 title 은 viewWillApprear 에서 정해줄 수 있다.

 

Previewing UIKit views

UIViewControllerRepresentable을 준수하면 view controller 도 preview 를 볼 수 있다! 와우

 

Hosting UIKit view with data dependencies

SwiftUI 의 Slider를 UISlider로 바꿔보자

어떤 상황이냐면 UIKit의 slider는 thumbTintColor 프로퍼티가 있지만 SwiftUI Slider는 해당 프로퍼티가 없다. 그래서 UISlider를 통해 그 프로퍼티에 접근하려 한다.

프로세스는 view controller 를 hosting 하는 것과 비슷하다.

  1. UIViewRepresentable를 준수하는 SwiftUI view 를 만든다.
  2. UIKit view를 인스턴스화 하기 위한 make 메서드를 구현한다.
  3. UIKit view를 SwiftUI view 로부터 업데이트 할 수 있는 update 메서드를 구현한다.
  4. Coordinator 를 만들고 target-action 메서드를 구현하여 SwiftUI view 로부터 UIKit view 를 업데이트 한다.

*간단한 UIView를 빠르게 wrapping 하려면 generic type 을 쓰는게 좋다. 아래 참고

 

Inline wrapping of UIKit or AppKit views within SwiftUI | Swift by Sundell

Making wrapping views a lot easier.

www.swiftbysundell.com


Updating UIView from SwiftUI

한가지 다뤄야 하는 문제점은 UIKit과 SwiftUI의 color type 이 다르다는 점이다.

SwiftUI의 Color 는 View이고 UIColor는 View 가 아니다. 다행히 UIColor 값으로 부터 Color view를 만들 수 있다. Color(UIColor.red)

다른 문제점은 UISlider(Float) 와 Slider(Double) 의 값 타입이 다르다. 

가장 쉬운 방법은 Double 로 부터 Float를 만드는 것 이다.

ColorUISlider의 @Binding 은 ContentView의 @State를 참조하는 것 이다.


UIView 와 SwiftUI view 의 데이터 동등하게 다루기

updateUIView(_:context:) 에 uiview.value = Float(self.value)로 업데이트 된 값을 할당 해주는 코드 작성

이렇게 UIKit control 이 SwiftUI 의 값을 받을 수 있다.

SwiftUI view 의 ColorUISlider가 UIKit control 의 UISlider의 데이터와 동기화 되도록 하는 Coordinator 가 필요하다.

위와 같이 ColorUISlider 구조체 안에 Coordinator class 를 만들면 makeCoordinator() 메서드를 구현해 주어야 한다.

이로써 coordinator 에세 parent ColorSlider(underlying UIView 는 UISlider) 를 연결해 주었다. 

coordinator의 목적은 UISlider의 값을 ColorSlider프로퍼티 값에 전달하는 것 이다. ContentView에 있는 @State 변수를 참조하는 것 인데 바인딩 됨으로써  UISlider 값이 ContentView를 효과적으로 업데이트 시킬 수 있는 것 이다.

UISlider 값을 받기 위해 coordinator 는 UISlider control 이벤트인 valueChanged를 구현해야 한다. 해당 이벤트를 target-action 으로 parent view의 makeUIView(context:) 내에 명시해 준다.

전형적인 UIKit 의 addTarget(_:axtion:for:) 메서드를 통해 UISlider control 의 valueChagned 이벤트의 Target을 Coordinator의 updateColorUISlider(_:) action 으로 잡았다.

*underlying UIView가 꼭 control이 있어야 되는건 아니다. MKMapViewDelegate 와 같은 delegate protocol 도 적용 가능하다.

이로써 UISlider 와 ColorUISlider와의 쌍방향 소통이 가능해 졌다.

ColorUISlider 를 ContentView 에 적용

적용전
적용 후 

 

728x90