UIKit를 실시간으로 미리보기하기 (Preview in UIKit)

UIKit를 실시간으로 미리보기하기 (Preview in UIKit)

·

4 min read

블로그를 이전하며 2022년 05월 11일 작성하였던 글을 재작성했습니다.


최근에 새롭게 UIKit를 활용한 앱을 완전히 코드만으로 (programmatically) 개발하는 것에 도전하기 시작했다. 그런데 얼마 전까지 만든 SwiftUI의 Preview가 너무 편한 시스템이라 이걸 UIKit에서도 사용할 수 없을지 알아보았는데, 역시! 사람들이 안했을리가 없다. 그래서 새로하는 프로젝트에 적용해보았고 결과는 대성공! 그리고 SwiftUI를 사용한 Preview에서 여러가지 유용한 속성들을 소개한다. 전체 코드는 글 제일 아래에 있다.

아래 사진은 내가 사용하고 있는 화면이고 Preview 아래쪽에 보이는 압정 아이콘을 클릭하면 다른 코딩 화면에서도 계속 Preview를 확인하면서 작업할 수 있다.

image

방법은 다음과 같다.

  1. UIViewControllerRepresentable를 상속받는 구조체를 만든다.
  2. 구조체 안에 필요한 요소들을 정의한다.(makeUIViewController, updateUIViewController)
  3. PreviewProvider를 상속받는 구조체를 만들어 Preview 화면을 띄운다.
  4. 용도에 맞게 미리보기 화면의 속성을 바꾼다.

우선 내가 코딩한 상황은 ViewController의 화면을 Preview 하는 것이다. 그리고 ViewController안에는 SwiftUI로 작성한 CircleView를 ViewController 안에 2개의 subview로 넣었다. Apple Developers | UIViewControllerRepresentable에서 UIViewControllerRepresentable에 대해 더 알아볼 수 있다.

1. UIViewControllerRepresentable를 상속받는 구조체 만들기

구조체를 만들기 전에 나는 파일을 따로 분리해두었다. 이유는 모든 뷰에 SwiftUI를 import 하지 않아도 되고 변수에 할당한 뷰만 바꿔주면 바로 Preview에 띄울 수 있기 때문이다. 사진에서의 PreviewController 파일에 Preview를 띄우는 모든 과정이 있다.

image

import SwiftUI

struct RealtimeView: UIViewControllerRepresentable{

}

SwiftUI를 import 하여 UIViewControllerRepresentable을 상속한 구조체를 만들어준다. 나는 RealtimeView라고 했다. (이름 다시 지어야겠다. 중구난방이고 통일성이 없다.) 애플 문서에서 설명하는 UIViewControllerRepresentable은 UIViewController 객체를 SwiftUI에서 생성하고 관리하기 위해 사용된다고 설명한다. 그래서 UIViewController를 사용해 만들어진 UIKit의 뷰를 SwiftUI 방식으로 Preview 하는데 사용할 수 있는 것이다. 이렇게 적었다면, 필요한게 없다며 Xcode가 오류를 표시할 것이다. 그러면 2단계로 넘어갈 준비가 다 된것이다!

2. 구조체 안에 필요한 요소들을 정의하기

targetView, UIViewControllerType, makeUIViewController, updateUIViewController를 정의해 줄 것이다. 앞의 두 개는 필수는 아니지만 효율성을 위해 정의한다. 뒤의 두 가지는 필수로 정의하도록 하는데, 이 두 가지가 있어야 오류가 없이 정상적으로 작동한다. Xcode의 오류 설명에 있는 빨간 동그라미를 눌러서도 추가할 수 있다. 코드 설명은 주석으로 되어있다.

import SwiftUI

struct RealtimeView: UIViewControllerRepresentable{

    typealias UIViewControllerType = ViewController //목표인 ViewController를 typealias한다. 띄우고싶은 뷰로 필요할 때마다 바꾼다.
    let targetView = ViewController() //마찬가지로 목표하는 ViewController를 한다. ()를 쓰는 걸 잊지 말자.

    func makeUIViewController(context: Context) -> UIViewControllerType {
        // ViewController를 리턴하는 함수. 필수이고 꼭 return 값이 있어야한다. targetView를 매번 찾아 바꾸기 귀찮으므로 위에서 미리 선언한 것이다.
        return targetView
    }

    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        // 나는 아직 쓸 필요가 없어서 비워두었다.
    }

}

오류는 없어지지만, 뷰는 뜨지 않는다. 이제 진짜 Preview를 띄우는 작업이 필요하다.

3. PreviewProvider를 상속받는 구조체를 만들어 Preview 화면을 띄우기

SwiftUI의 View처럼 쓸 수 있는 구조체를 만들었으니 띄우는 일만 남았다. PreviewProvider를 상속한 구조체를 만든다. 이름은 아무거나 상관없는데, 나는 ViewPreview라고 했다. 그리고 static var previews: some View{}에서 {} 안에 표기하고 싶은 View를 써넣으면 된다. 그래서 나는 CircleView라는 이름의 SwiftUI로 작성한 View와 이 CirclewView 두 개를 subview로 가지고 있는 UIKit로 작성한 ViewController가 어떻게 보이는지 확인해보았다. 아래의 코드와 사진이 결과물이다.

struct ViewPreview: PreviewProvider {
    static var previews: some View{
        CircleView()
            .previewLayout(.sizeThatFits)
        RealtimeView()
    }
}

image

완전한 미리보기가 뜰 것이다. 아주 손쉽게 완성 되었다! 그리고 이제 속성을 바꿔줌으로써 아이폰 기종을 다양화하거나 CircleView를 미리보기한 것처럼 다양하게 표시할 수 있다.

4. 용도에 맞게 미리보기 화면의 속성을 바꾸기

Group 활용하기

Group을 활용해 다양한 Preview에 속성을 한 번에 지정할 수 있다. 아래 사진처럼 두 가지 미리보기에 대해 크기를 한 번에 지정해주었다.

image


        Group{
            RealtimeView()
            RealtimeView()
        }
        .previewLayout(.fixed(width: 300, height: 120))

ForEach 활용하기

For Each를 사용해서 두 가지 종류의 디바이스에 대한 미리보기를 구현했다.

image

        ForEach(["iPhone SE (2nd generation)", "iPhone XS Max"], id: \.self) { deviceName in
                RealtimeView()
                    .previewDevice(PreviewDevice(rawValue: deviceName))
                    .previewDisplayName(deviceName)
        }

사용할 수 있는 유용한 속성들

속성 이름개요
previewDisplayName미리보기의 이름을 변경해준다. 기본으로는 "Preview"로 되어있다.
previewDevice미리보기할 디바이스를 선택할 수 있다.
previewLayout미리보기의 frame을 지정한다는 느낌이다. 뷰 크기에 맞게하거나 특정한 크기로 지정하는 등 자유롭게 설정할 수 있다.

List of devices that can be previewed in Swift UI에서 DeviceName을 검색해 활용하면 편리하다.

전체 코드


import SwiftUI

struct RealtimeView: UIViewControllerRepresentable{

    typealias UIViewControllerType = ViewController
    let targetView = ViewController()

    func makeUIViewController(context: Context) -> UIViewControllerType {
        return targetView
    }

    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {

    }

}

struct ViewPreview: PreviewProvider {
    static var previews: some View{
        CircleView()
            .previewLayout(.sizeThatFits)
        RealtimeView()
        /*Group{
            RealtimeView()
            RealtimeView()
        }
        .previewLayout(.fixed(width: 300, height: 120))*/
        /*ForEach(["iPhone SE (2nd generation)", "iPhone XS Max"], id: \.self) { deviceName in
                RealtimeView()
                    .previewDevice(PreviewDevice(rawValue: deviceName))
                    .previewDisplayName(deviceName)
        }*/
    }
}

여담

UIKit로 기본적인 View를 만드는 건 어렵지 않은데, 나만의 요소를 만드는 것이 너무 힘들었다. 그래서 알아보니 SwiftUI를 UIKit에서 사용하는 방법도 있어서 위와 같이 도형 등에 있어서는 SwiftUI를 활용해 개발하고있다.