実機１台で別端末のレイアウトを確認する方法


電車内でiPhone5というかSEを使っている人を見ると「まだこつい５使ってんのかよ」と思ってしまいます。奴らは最近主流のiPhone8やiPhoneXなどの端末と比べると一回り小さいため、iPhoneSEで起動してみると実はレイアウトが崩れてたなんてことが頻繁に起こってしまいます。

なにか解決策はないかと軽くググってみたら、下記の記事を見つけました。
https://qiita.com/usagimaru/items/dbd9ed49b04e113eb6de
これを参考にすれば実機でお手軽にディバイス別のレイアウトを確認することができそうだと思いました。


#### デモ

作成したDebugWindowをAppDelegateで指定します。
すると右上にDと書かれたボタンが表示されタップするとディバイス一覧が表示され、対象のディバイスを選択するとwindowのサイズが対象ディバイスのサイズに変更されます。


``` .swift
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = DebugWindow()
        window?.rootViewController = ViewController()
        window?.makeKeyAndVisible()
        return true
    }
```


![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166241/738d783e-ab79-4b49-34b6-52d0678f9d4b.gif)



#### ソース
 
``` .swift
enum Device: CaseIterable {
    // iPhone SE
    case inch4
    // iPhone 6 iPhone 6s iPhone 7 iPhone 8
    case inch4_7
    // iPhone 6 Plus iPhone 6s Plus iPhone 7 Plus iPhone 8 Plus
    case inch5_5
    // iPhone X
    case inch5_7
    // iPhone XR
    case inch6_1

    var size: CGSize {
        switch self {
        case .inch4: return .init(width: 320, height: 568)
        case .inch4_7: return .init(width: 375, height: 667)
        case .inch5_5: return .init(width: 414, height: 736)
        case .inch5_7: return .init(width: 375, height: 812)
        case .inch6_1: return .init(width: 414, height: 896)
        }
    }

    var label: String {
        switch self {
        case .inch4: return "iPhone SE"
        case .inch4_7: return "iPhone 6 iPhone 6s iPhone 7 iPhone 8"
        case .inch5_5: return "iPhone 6 Plus iPhone 6s Plus iPhone 7 Plus iPhone 8 Plus"
        case .inch5_7: return "iPhone X"
        case .inch6_1: return "iPhone XR"
        }
    }
}

final class DebugWindow: UIWindow {
    override init(frame: CGRect) {
        super.init(frame: frame)
        configure()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private lazy var menuButton: UIButton = {
        let it = UIButton()
        it.addTarget(self, action: #selector(actionTap), for: .touchUpInside)
        it.backgroundColor = UIColor(red: 55 / 255, green: 132 / 255, blue: 233 / 255, alpha: 1)
        it.setTitle("D", for: .normal)
        it.setTitleColor(.white, for: .normal)
        it.titleLabel?.font = .boldSystemFont(ofSize: 20)
        it.layer.cornerRadius = 20
        it.clipsToBounds = true
        return it
    }()
    
    override func didAddSubview(_ subview: UIView) {
        super.didAddSubview(subview)
        bringSubviewToFront(menuButton)
    }
}

private extension DebugWindow {
    func configure() {
        addSubview(menuButton)
        menuButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            menuButton.topAnchor.constraint(equalTo: topAnchor, constant: 40),
            menuButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -40),
            menuButton.widthAnchor.constraint(equalToConstant: 40),
            menuButton.heightAnchor.constraint(equalToConstant: 40)
            ])
    }
    
    @objc func actionTap() {
        let sheet = UIAlertController(title: "select device", message: nil, preferredStyle: .actionSheet)
        Device.allCases.forEach { device in
            sheet.addAction(UIAlertAction(title: device.label, style: .default) { _ in
                self.adjustSize(device: device)
            })
        }
        sheet.addAction(UIAlertAction(title: "cancel", style: .cancel))
        topViewController()?.present(sheet, animated: true)
    }
    
    func adjustSize(device: Device) {
        let bounds = UIScreen.main.bounds
        
        UIView.animate(withDuration: 0.25) {
            self.frame = bounds.insetBy(dx: (bounds.width - device.size.width) / 2, dy: (bounds.height - device.size.height) / 2)
        }
        rootViewController = ViewController() // RootViewControllerもしくは検証したいViewControllerを指定
        bringSubviewToFront(menuButton)
    }
    
    func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(controller: navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(controller: selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(controller: presented)
        }
        return controller
    }
}

```

https://qiita.com/tomohisaota/items/f8857d01f328e34fb551

#### 問題点⚠
- ViewがAutoLayoutで組まれていることが前提です。
- UIScreen.main.boundsは変わらないので、これを条件にレイアウトを組んでいる場合は検証になりません
- SafeAreaがなくなるから、iPhoneX以上の検証が無意味になる。
- 実はアラート表示時にWarningが出ているのは秘密👮




#### 感想
- いけると思ったけど全然ダメでした。
- 使用箇所は限定的でしたが、さくっと試せて便利といえば便利でした。
- ぶっちゃけAutoLayoutうんぬんじゃなくてラベルの文字がよく崩れます。NSAttributedStringとUILabelのadjustsFontSizeToFitWidthが一緒に使えればいいのですが。
- どうでもいいですがiPhone8のサイズでデザインが上がってきて、それどおりにマージンなどを組むとSEでレイアウトが崩るのでview.bottom + 60 などデザイン上のマージンの値をそのまま使うのではなく、画面全体に対する比率でレイアウトを組むように心がけています。