SwiftでStoryboardを消してコードのみで実装する方法

# はじめに
どうもこんにちは〜@kaneko77です。
今回は複数人開発で厄介なStoryboardを抹消してコードのみで実装したいと思います。
オートレイアウトを適用する際はやはりinterface builderの方が楽ですけどね笑
IBOutletとコードで実装する場合だとデフォルトのプライオリティが若干違うとかあるので
そこら辺は気をつけてください。

# こんな方対象
- 忌まわしいStoryboardを消してコードのみで書きたい...
- swiftUI使いたくないけどコードで書きたい
- 複数人開発でプロジェクト作んないといけない

# 目次
 - `Main.storyboard`を消す
 - カスタムViewを作成してViewControllerに渡す方法
 - Push遷移の方法

それでは行って見ましょう〜〜〜

# 作業
## `Main.storyboard`を消す
まずプロジェクト作成します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397376/97989876-c4da-3033-fafd-7bc7df95a4be.png" width="500px">
`Main.storyboard`を消してください
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397376/05e2a2c6-98d2-a0a0-a99b-913f647190a6.png" width="500px">
次に`Main interface`を消します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397376/dfc3320e-eaf1-9621-7d4e-f63137f7332f.png" width="500px">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397376/81e166d1-09cb-0df9-b252-39885956dd0a.png" width="300px">
まだやることあります。
次に`info.plist`のプロパティを開いて`Storyboard Name`を消します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397376/8b5e8e57-a84e-4df0-87f7-5e6e8924b1e8.png" width="500px">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397376/ed572398-3200-ad20-0c4a-45ae4296ac89.png" width="500px">

以上で抹消作業完了となります。

## カスタムViewを作成してViewControllerに渡す方法
### windowの作成
今の状態でビルドするとinterface builderが消えたので真っ黒な画面が表示されると思います。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397376/c126c2bd-a52d-2d1a-6bab-41a3e516ded1.png" width="300px">

下記の`SceneDelegate`をいじります。


```swift
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let _ = (scene as? UIWindowScene) else { return }
    }
}
```

下記が修正したコードになります。
説明すると`ViewController`を配置するにはwindowが必要ですのでwindowを作成します。
その後windowの上に`ViewController`をセットして
最後windowを最も前面に表示するメソッドを呼び出しています。

```swift
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = ViewController.init()
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}
```

### カスタムViewの作成
これで`ViewController`を呼べるようになりました。
これであとはカスタムViewを`ViewController`に配置することで期待するレイアウトを反映されるようになります。
下記がそのコードになります。
簡易的なコード書きました。

```swift
import UIKit

class ViewController: UIViewController {

    override func loadView() {
        super.loadView()
        self.view = MainView(frame: .zero)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }


}


class MainView: UIView {

    let hogeLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "hogeeeeee"
        label.textColor = .blue
        label.backgroundColor = .green
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = .red
        setComponent()
        setConstraint()
    }
    
    private func setComponent(){
        addSubview(hogeLabel)
    }
    
    private func setConstraint(){
        NSLayoutConstraint.activate([
            hogeLabel.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 50),
            hogeLabel.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20),
            hogeLabel.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397376/e9e3b686-d281-910f-e512-3e4ee798aa1c.png" width="300px">

## Push遷移の方法
### UINavigationControllerをつける
まず下記の`SceneDelegate`にて`UINavigationController`を付与させます。

```swift
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            //ここをUINavigationControllerにする
            window.rootViewController = UINavigationController(rootViewController: ViewController.init())
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}
```

UINavigationControllerを付与されたことが確認できます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397376/49053abc-7508-c836-61ae-e0454f415bab.png" width="300px">

### Push遷移するコードを書く
それでは遷移するコードをかいて行きます。
下記を参考に書いてください。

```swift
import UIKit

class ViewController: UIViewController {

    var mainView: MainView!
    
    override func loadView() {
        super.loadView()
        self.mainView = MainView(frame: .zero)
        self.view = self.mainView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        self.mainView.pushButton.addTarget(self, action: #selector(tapped(_:)),for: .touchUpInside)
    }

    // MARK: タッチイベント
    @objc func tapped(_ sender : Any) {
        self.navigationController?.pushViewController(PushViewController.init(), animated: true)
    }

}


class MainView: UIView {

    let hogeLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "hogeeeeee"
        label.textColor = .blue
        label.backgroundColor = .green
        return label
    }()
    
    let pushButton: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("遷移する", for: .normal)
        button.setTitleColor(.gray, for: .normal)
        button.backgroundColor = .yellow
        return button
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = .red
        setComponent()
        setConstraint()
    }
    
    private func setComponent(){
        [hogeLabel, pushButton].forEach{
            addSubview($0)
        }
    }
    
    private func setConstraint(){
        NSLayoutConstraint.activate([
            hogeLabel.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 50),
            hogeLabel.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20),
            hogeLabel.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20),
            
            pushButton.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -20),
            pushButton.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20),
            pushButton.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20),
            
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}
```

```swift
import UIKit

class PushViewController: UIViewController {

    var mainView: PushView!
    
    override func loadView() {
        super.loadView()
        self.mainView = PushView(frame: .zero)
        self.view = self.mainView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

}


class PushView: UIView {

    let hugaLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "hugaaaaaa"
        label.textColor = .white
        label.backgroundColor = .gray
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = .yellow
        setComponent()
        setConstraint()
    }
    
    private func setComponent(){
        addSubview(hugaLabel)
    }
    
    private func setConstraint(){
        NSLayoutConstraint.activate([
            hugaLabel.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 50),
            hugaLabel.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20),
            hugaLabel.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397376/3af3c051-4dc6-5471-91e4-5a03b866d509.png" width="300px">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397376/f1738eda-9f8d-a5c9-6fa6-a0598c5826f8.png" width="300px">

# 最後に
これまではオートレイアウトはインターフェースビルダーでオートレイアウトを組んでいましたが
コードで実装する必要があります。
そこだけ注意してください。
`label.translatesAutoresizingMaskIntoConstraints = false`
上記のコードを入れて行いとオートレイアウトが適用されないので注意してください。(NSLayoutConstraintで組む場合は)
オートレイアウトに関しては[Snapkit](https://github.com/SnapKit/SnapKit)や[Cartography](https://github.com/robb/Cartography)のライブラリを導入することをお勧めします。
スクロールViewなどがちゃんとオートレイアウトをしっかりと理解していないと詰まる部分だと思います。
後々スクロールViewのオートレイアウトについては記事作成します。
いかがでしたでしょうか？
割と簡単ですし、新しいプロダクトのSwiftUIの採用率は増えてると思いますがしかし、IOS１３からですのでもう少し時間かかると思います。
このやり方を覚えて複数人開発する際に面倒なコンフリクト対応など少しでも減らせれば幸せになれそうですね。
ここまで読んでいただきありがとうございます。