티스토리 뷰

안녕하세요 Out of code 입니다.

 

UI를 개발하는것은 굉장히 복잡하며 미세하여 제대로 가이드 수치를 맞춰 두어도 확인하기 위해서는 빌드를 하면서 확인합니다.

이전까지는 스토리 보드에서 개발을 할때 느린 프리뷰를 보고 개발을 하고 실제 동작을 해보고 하는 작업들이 너무 지치게 했습니다.

 

* 물론 Storyboard에 Preview 기능이 있기는 하지만 이 부분은 많은 버그와 느린 속도로 인하여 사용성이 좋지 않죠.

* 저는 View를 만드는 방법도 이제는 스토리보드를 사용하지 않습니다. 너무 느리거든요!!

 

저는 코드에서 바로 만들기 때문에 SwiftUI처럼 코드에서 바로 프리뷰를 보고 싶었어요.

그래서 SwiftUI처럼 Preview를 사용 할 수 있는 방법을 공유하려고 합니다.

 

지금 이 글을 보는 여러분들은 아마도 xcode 11 이라고 생각하고 있습니다.

아니라면 지금 바로 업그레이드 하세요!!

 

저는 Deployment Target도 9.0입니다.

방법은 #if #endif문을 이용하여 하는 방법입니다.

그것과 함께 우리가 SwiftUI의 Preview를 보여주기 위해서는 ~~Representable을 이용하여야 하는데요.

실제로 동작을 시켜보면 Autolayout의 적용은 UIViewControllerRepresentable에서만 잘 나오더군요.

버그인건지...

 

ViewController만 설명을 하지만 제가 만든 방식대로면 다른 작은 뷰들도 만들고 ViewController의 중앙에 배치하는 방식으로 한번 해봐도 좋지 않을까요?

방법은 궁금하시면 댓글 남겨주시면 코드 남길게요.

#if canImport(SwiftUI) && DEBUG
import SwiftUI

@available(iOS 13.0, *)
extension UIViewController {
    private struct ViewControllerRepresentable: UIViewControllerRepresentable {
        private let viewController: UIViewController
        
        init(with viewController: UIViewController) {
            self.viewController = viewController
        }
        
        func makeUIViewController(context: Context) -> some UIViewController {
            return viewController
        }
        
        func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
            // Not Use
        }
    }
    
    func toPreview() -> some View {
        ViewControllerRepresentable(with: self)
    }
}

#endif

UIViewController를 extension 하였는데 #if안에 들어가서 canImport하게 될 경우에만 사용이 가능합니다.

그러면 빌드 오류가 나지 않고 잘 되겠죠.

 

자 그리고 Preview를 보여줄 ViewController에 다음과 같이 만들어 봅시다.

extension 을 하여서 contentView를 내부에 만들어 주면 더 깔끔하지 않을까요?

import UIKit

#if canImport(SwiftUI) && DEBUG
import SwiftUI

@available(iOS 13.0, *)
struct ViewControllerPreview: PreviewProvider {
    static let deviceNames = [
        "iPhone SE",
        "iPhone 11 Pro Max",
        "iPhone 7"
    ]
    
    static var previews: some View {
        ForEach(deviceNames, id: \.self) { deviceName in
            ViewController().toPreview()
                .previewDevice(.init(rawValue: deviceName))
                .previewDisplayName(deviceName)
        }
    }
}
#endif

class ViewController: UIViewController {
    
    private lazy var contentView = ContentView()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(contentView)
        NSLayoutConstraint.activate([
            contentView.leftAnchor.constraint(equalTo: view.leftAnchor),
            contentView.rightAnchor.constraint(equalTo: view.rightAnchor),
            contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            contentView.heightAnchor.constraint(equalToConstant: 200)
        ])
    }
}

extension ViewController {
    final class ContentView: UIView {
        private lazy var button: UIButton = {
            let button = UIButton()
            button.translatesAutoresizingMaskIntoConstraints = false
            button.backgroundColor = .blue
            button.setTitle("Test", for: .normal)
            button.heightAnchor.constraint(equalToConstant: 100).isActive = true
            return button
        }()
        
        private lazy var imageView: UIImageView = {
            let imageView = UIImageView()
            imageView.translatesAutoresizingMaskIntoConstraints = false
            
            let image = UIImage(named: "apple.png")
            guard let size = image?.size else { return imageView }
            
            let height: CGFloat = 60.0
            let width = (height / size.height) * size.width
            
            imageView.image = image
            
            NSLayoutConstraint.activate([
                imageView.heightAnchor.constraint(equalToConstant: height),
                imageView.widthAnchor.constraint(equalToConstant: width)
            ])
            
            return imageView
        }()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            
            backgroundColor = .lightGray
            translatesAutoresizingMaskIntoConstraints = false
            
            addSubview(button)
            NSLayoutConstraint.activate([
                button.leadingAnchor.constraint(equalTo: leadingAnchor),
                button.trailingAnchor.constraint(equalTo: trailingAnchor),
                button.bottomAnchor.constraint(equalTo: bottomAnchor),
                
            ])
            
            addSubview(imageView)
            NSLayoutConstraint.activate([
                imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
                imageView.topAnchor.constraint(equalTo: topAnchor)
            ])
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
}

위쪽에 PreviewProvider를 상속받아서 사용하게 한 부분이 주요한 부분입니다.

deviceNames를 넣어서 여러개의 device의 preview를 한번에 볼수 있도록 할 수도 있습니다.

아래쪽은 View를 만들어주기 위해서 하는 부분이라 여러분이 해보고 싶은대로 해보시는것도 좋습니다.

대신에 수정하고 오른쪽에 Resume만 눌러주면 고친 부분이 자동으로 반영됩니다.

 

마치 SwiftUI를 고치는거처럼요.

 

한가지 주의할것은 deviceNames를 너무 많이 넣으면 성능이 안 좋아져서 오히려 안 좋으니까 zeplin 기준으로 나온 디자인은 보고 나머지는 변경해가면서 돌려보는것도 좋을거 같습니다.

 

위의 예제에서 button title을 test -> 타이틀입니다. 로 변경하였습니다. 바로 변경이 되요.

 

시뮬레이터와도 비교를 해봅니다.

시뮬레이터와도 차이가 없네요!! 당연하겠죠? ㅋㅋ

질문은 남겨주시고 이후에 SwiftUI처럼 뷰를 Stackable하게 쌓아서 만드는 방법도 구현했는데 이건 추후에 공개할게요.

 

마지막으로 소스입니다.

 

github.com/outofcode-example/iOS-XCodePreview

 

outofcode-example/iOS-XCodePreview

Contribute to outofcode-example/iOS-XCodePreview development by creating an account on GitHub.

github.com

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함