본문 바로가기

iOS/iOS (기본)

[iOS] Preview (실시간 미리보기)

1️⃣ Preview란?


Xcode에서는 실제 기기 및 시뮬레이터를 사용하지 않고 최신 상태의 뷰 콘텐츠를 표시할 수 있는 Preview 기능이 있습니다. 해당 기능을 사용하면 화면개발을 할 때 시간을 단축할 수 있도록 도움을 줍니다.

 

1-1. Preview의 동작원리


기본적으로 SwiftUI 프로젝트를 생성하면 하단의 Preview를 표시하는 코드가 생성됩니다.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
  1. 현재 소스 에디터에 PreviewProvider프로토콜을 준수하는 타입이 존재하는지 확인
  2. PreviewProivder프로토콜의 필수 구현 사항인 previews 타입 프로퍼티에서 뷰 생성
  3. 액티브 스킬의 목적지로 선택한 시뮬레이터 또는 맥에 연결한 기기의 형태로 preview container 렌더링
  4. 리뷰 컨테이너를 직접 지정해 줄 경우 3번 에서 선택한 기기를 무시하고 해당 기기 형태로 렌더링

Preview는 수정결과가 자동으로 반영이 되지만 일부 수정결과에 대해서는 수동으로 빌드를 해야하는 경우가 있습니다.

  • 수동으로 빌드해야하는 경우
    1. 프로퍼티와 메서드를 추가 / 제거 / 수정하는 경우
    2. 저장 프로퍼티의 값 변경
    3. 뷰의 타입 이름을 변경하거나 또 다른 뷰를 추가할 때
    4. 앱을 수동으로 빌드하는 경우

 

1-2. Preview의 수식어


Preview에서는 수정자를 활용하여 여러가지 옵션을 설정할 수 있습니다.

 

.preferredColorScheme(.dark)  다크모드 설정하기
.previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro Max")) preview device변경
.previewDevice("iPhone 12") 위의 수정자와 동일한 기능
.previewLayout(.fixed(width: 400, height: 700)) 너비 400, 높이 700인 preview 표시
.previewLayout(.sizeThatFits) size 만큼만 preview 표시

 

2️⃣ UIKit에서 Preview 사용하는 방법


UIKit에서 Preview를 사용하기 위해서는 Xcode 11이상, macOS Catalina이상, iOS13이상이여야 기능을 사용할 수 있다.

 

2-1. 환경 설정


  • iOS 13이상에서 SwiftUI프레임워크, Debug모드에서만 동작하도록 설정합니다.
#if canImport(SwiftUI) && DEBUG
import SwiftUI
@available(iOS 13.0, *)

#endif

 

2-2. UIViewRepresentable 준수하는 View 생성


💡 UIView를 사용하는 경우 UIViewRepresentable을 채택하고, UIViewController를 사용하는 경우 UIViewControllerRepresentable을 채택합니다
  • UIView를 생성한 후 UIViewRepresentable프로토콜을 준수하는 ViewRepresentable구조체 생성

 

2-3. PreviewProvider 생성


생성한 ViewRepresentable구조체를 Preview에서 생성

 

2-4. Extension으로 활용


 

UIViewController+Extension.swift

더보기
#if DEBUG
import SwiftUI

extension UIViewController {
    
    /// PreView 동작하도로 도와 주는 구조체
    struct Preview: UIViewControllerRepresentable {
        let viewController: UIViewController
        
        func makeUIViewController(context: Context) -> UIViewController {
            return viewController
        }
        
        func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        }
    }

    func toPreview() -> some View {
        Preview(viewController: self)
    }

}

/// 프리뷰 생성 도와주는 프로토콜
protocol PreviewSupporter {
    associatedtype UIKitView: View
    
    static func makeAllDeviceXcode14(storyboardName: String, identifierName: String, device: Xcode14Device) -> UIKitView
}

extension PreviewSupporter {

    static func makeAllDeviceXcode14(storyboardName: String, identifierName: String, device: Xcode14Device) -> some View {
        UIStoryboard(name: storyboardName, bundle: nil).instantiateViewController(identifier: identifierName).toPreview()
                    .previewDevice(PreviewDevice(rawValue: device.rawValue))
                    .previewDisplayName(device.rawValue)
    }
    
}

/// 자동 생성은 Xcode메뉴에서 Editor -> Create Preview
/// 프리뷰 사용 예시 - NewLoginHomeViewController  온보딩 로그인화면 예시
struct ExamplePreView: PreviewProvider, PreviewSupporter {
    static var previews: some View {
        makeAllDeviceXcode14(storyboardName: "NewLoginHomeViewController", identifierName: "NewLoginHomeViewController", device: .iPhone14)
        makeAllDeviceXcode14(storyboardName: "NewLoginHomeViewController", identifierName: "NewLoginHomeViewController", device: .iPhone14Pro)
        makeAllDeviceXcode14(storyboardName: "NewLoginHomeViewController", identifierName: "NewLoginHomeViewController", device: .iPhone14Plus)
        makeAllDeviceXcode14(storyboardName: "NewLoginHomeViewController", identifierName: "NewLoginHomeViewController", device: .iPadPro12)
        makeAllDeviceXcode14(storyboardName: "NewLoginHomeViewController", identifierName: "NewLoginHomeViewController", device: .iPhoneSE3)
    }
}

/// Xcode14에 내장된 기기 목록
enum Xcode14Device: String {
    case iPhone14 = "iPhone 14"
    case iPhone14Pro = "iPhone 14 Pro"
    case iPhone14Plus = "iPhone 14 Plus"
    case iPhoneSE3 = "iPhone SE (3rd generation)"
    case iPadPro12 = "iPad Pro (12.9-inch) (6th generation)"
    case iPadPro11 = "iPad Pro (11-inch) (4th generation)"
    case iPadMini6 = "iPad mini (6th generation)"
    case iPad10 = "iPad (10th generation)"
    case iPadAir5 = "iPad Air (5th generation)"
}

#endif

 

3️⃣ Swift매크로 활용 (iOS17이상)


iOS 17이상 Xcode 15이상에서 기존의 Preview방식을 더욱 간단하게 사용하기 위해 매크로를 활용할 수 있습니다.

 

3-1. 활용 방법


SwiftUI

#Preview {
    ContentView()
}

// name파라미터를 전달하여 Preview의 displayname을 전달할 수 있습니다.
#Preview("Sample") {
    ContentView()
}

UIKIt

#Preview {
		let vc = ViewController()
		return vc
}

// name파라미터를 전달하여 Preview의 displayname을 전달할 수 있습니다.
#Preview("Sample") {
		let vc = ViewController()
		return vc
}

// Storyboard
#Preview("Sample") {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
 
    var vc = storyboard.instantiateViewController(withIdentifier: "ViewController")
 
    return controller
}

 

3-2. 응용


// 2개의 Preview 생성
#Preview("Article List View") {
    ArticleListView()
}
 
#Preview("Article View") {
    ArticleView()
}

// traits 활용 - landscape 모드 설정
#Preview("Article List View", traits: .landscapeLeft) {
    ArticleListView()
}

// traits 활용 - 레이아웃 설정
#Preview("Article List View", traits: .fixedLayout(width: 300, height: 300)) {
    ArticleListView()
}

#Preview("Article List View", traits: .sizeThatFitsLayout) {
    ArticleListView()
}

 

4️⃣ Preview 에러 및 Tip


4-1. Preview is missing EnvironmentObject


에러 사항 : EnvironmentObject를 선언하고 Preview에서 인스턴스 생성해주지 않으면 발생하는 이슈

해결

struct RideRequestView: View {
    
    @State private var selectedRideType: RideType = .uberX
    @EnvironmentObject var locationViewModel: LocationSearchViewModel

		...
}

struct RideRequestView_Previews: PreviewProvider {
    static var previews: some View {
        RideRequestView()
            .environmentObject(LocationSearchViewModel())
    }
}
  • .environmentObject안에 인스턴스 생성을 하면 해결됩니다.

 

4-2. Canvas 창 표시방법


 

🔗 참고링크


'iOS > iOS (기본)' 카테고리의 다른 글

[iOS] iOS 14이상에서 UICollectionView 사용하기  (1) 2024.04.18