SwiftUI 2.0 & UIKit & Storyboard (Part 1)

# Part 1: SwiftUI 2.0 & Storyboard
The article is broken in three different parts:

- Part 1: SwiftUI 2.0 & Storyboard ([日本語](https://qiita.com/loic-meister-guild/items/b62f2bc90cc6a7f8f92a))
- [Part 2: SwiftUI 2.0 & UIKit](https://qiita.com/loic-meister-guild/items/a880dbc12dc844cbacaa)
- [Part 3: Storybard & SwiftUI 2.0](https://qiita.com/loic-meister-guild/items/dc24c7319baba675564c)

The first part is about using storyboard elements in a SwiftUI 2.0 application.

## Use a Storyboard view controller in SwiftUI
To be able to use a view controller defined in a XCode storyboard in SwiftUI, It has to be wrapped in a type conforming [`UIViewControllerRepresentable`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable) protocol. This is useful for reusing parts of a legacy app or doing things that are possible in the storyboard and UIKit but not yet in SwiftUI.

### XCode Project
Create a new SwiftUI app project
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/2eb13d16-1b15-5b6c-0a80-963291f9665d.png)
Name the project
![Screen Shot 2021-05-19 at 15.40.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/72e884d8-197f-7728-d20f-2f3b2f2c96b9.png)
Create a new storyboard file
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/b1a32fd5-d04f-3ba2-cc04-f56cb9ddbcaf.png)
Name the storyboard
![Screen Shot 2021-05-19 at 15.45.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/d9b94e5d-fe97-59aa-6142-0c307a29bf4a.png)
Then populate the default `UIViewController` with UI elements and make the initial view controller
![Screen Shot 2021-05-19 at 22.50.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/a741f2b2-e277-a350-6cd3-c2dd1e561632.png)
Now we have a SwiftUI project containing a storyboard but the default `ContentView` from the SwiftUI project is still being displayed.
We need to connect them ...
### StoryboardViewController: The custom `UIViewController` class
First, we will add a custom `UIViewController` class to our storyboard view controller...

Create a Swift file for the `StoryboardViewController` class, our subclass of `UIViewController`:

```Swift:StoryboardViewController.swift
import SwiftUI

class StoryboardViewController: UIViewController {
}
```
Then connect the class to the UI elements of the storyboard view controller's layout using `IBOutlet`s and `IBAction`s:
- `labelText`: an `IBOutlet` connected to the second `Label` element
- `inputText`: an `IBOutlet` connected to the `TextField` element
- `changeText(_:)`: an `IBAction` connected to the `TouchUpInside` event for the "Change Text!" `Button`
- `goBack(_:)`: an `IBAction` connected to the `TouchUpInside` event for the "Go Back!" `Button`

```Swift:StoryboardViewController.swift
class StoryboardViewController: UIViewController {
    @IBOutlet weak var labelText: UILabel!
    @IBOutlet weak var inputText: UITextField!
    @IBAction func changeText(_ sender: Any) {
        // ...
    }
    @IBAction func goBack(_ sender: Any) {
        // ...
    }
}
```
Finally, add a reference to the delegate protocol (that will be defined in the next section) and call its methods in the corresponding `IBAction`s:

```Swift:StoryboardViewController.swift
import SwiftUI

class StoryboardViewController: UIViewController {
    var delegate: StoryboardViewControllerDelegate = DefaultStoryboardViewControllerDelegate()
    @IBOutlet weak var labelText: UILabel!
    @IBOutlet weak var inputText: UITextField!
    @IBAction func changeText(_ sender: Any) {
        delegate.didPressChangeText(self)
    }
    @IBAction func goBack(_ sender: Any) {
        delegate.didPressGoBack(self)
    }
}
```
### StoryboardViewControllerDelegate
Now, let's create the delegate protocol that will be used to communicate changes occurring within the storyboard view controller to other parts of the SwiftUI interface ...

Create a Swift file containing the 2 following types:

- `StoryboardViewControllerDelegate`: the delegate protocol used for `StoryboardViewController`
- `DefaultStoryboardViewControllerDelegate`: a default implementation of `StoryboardViewControllerDelegate`, which methods do nothing when called

```Swift:StoryboardViewControllerDelegate.swift
import Foundation

protocol StoryboardViewControllerDelegate: AnyObject {
    func didPressChangeText(_ storyboardVC: StoryboardViewController)
    func didPressGoBack(_ storyboardVC: StoryboardViewController)
}

class DefaultStoryboardViewControllerDelegate: StoryboardViewControllerDelegate {
    func didPressChangeText(_ storyboardVC: StoryboardViewController) {}
    func didPressGoBack(_ storyboardVC: StoryboardViewController) {}
}
```
The `StoryboardViewControllerDelegate` protocol has 2 methods:

- `didPressChangeText(_:)`: the method called when the user pressed the "Change Text!" button
- `didPressGoBack(_:)`: the method called when the user pressed the "Go Back!" button

For simplicity, both methods have only one parameter: the `StoryboardViewController` instance whose changes they observe.
### StoryboardView: The [`UIViewControllerRepresentable`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable) conforming type
Next, let's create the `StoryboardView` type to wrap the `StoryboardViewController ` and connect the Storyboard interface to the SwiftUI interface.

We need to implement the 2 requirements of the [`UIViewControllerRepresentable`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable) protocol:

- a [`makeUIViewController(context:)`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/makeuiviewcontroller(context:)) method that creates and configure a `UIViewController`.
- an [`updateUIViewController(_:context:)`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/updateuiviewcontroller(_:context:)) method that updates the state of the specified view controller with new information from SwiftUI. **We will leave it blank** as we won't use it for our example.

```Swift:StoryboardView.swift
import SwiftUI

struct StoryboardView: UIViewControllerRepresentable {
    func makeUIViewController(context: UIViewControllerRepresentableContext<StoryboardView>) -> StoryboardViewController {
        // ...
    }
    func updateUIViewController(_ uiViewController: StoryboardViewController, context: UIViewControllerRepresentableContext<StoryboardView>) {
    }
}
```
Then, we implement the [`makeUIViewController(context:)`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/makeuiviewcontroller(context:)) method to return an instance of the `StoryboardViewController` from the Storyboard when called by SwiftUI. SwiftUI will call this method once when it’s ready to display the view, and then it will manage the view controller’s life cycle.

To do that, we need to retrieve our `StoryboardViewController` from the storyboard:

- To retrieve the storyboard, simply instantiate a `UIStoryboard` object with the name of the storyboard file (here, the name of the storyboard is "Main" as the file is named "Main.storyboard"). 

```Swift:Swift
let storyboard = UIStoryboard(name: "Main", bundle: nil)
```

- To retrieve the `StoryboardViewController`, simply instantiate the storyboard initial view controller using the [`instantiateInitialViewController()`](https://developer.apple.com/documentation/uikit/uistoryboard/1616213-instantiateinitialviewcontroller)  method:

```Swift:Swift
let storyboardVC = storyboard.instantiateInitialViewController()
```
※ Another option would be to set a __storyboardID__ (like "storyboardView") on the view controller in the storyboard and instantiate it using the [`instantiateViewController(withIdentifier:)`](https://developer.apple.com/documentation/uikit/uistoryboard/1616214-instantiateviewcontroller) method with the view controller's __storyboardID__ as argument:

```Swift:Swift
let storyboardVC = storyboard.instantiateViewController(withIdentifier: "storyboardView")
```

- Set the `Coordinator` delegate object that will handle the event notifications from `StoryboardViewController` in `StoryboardView`:

```Swift:Swift
    storyboardVC.delegate = context.coordinator
```
※　We will discuss the `Coordinator` delegate object in the next section ...

- And last, return the `StoryboardViewController`:

```Swift:Swift
    return storyboardVC
```
### Coordinator: The `StoryboardViewController`'s delegate
To communicate changes occurring within the view controller to other parts of the SwiftUI interface, a custom *coordinator* object must be provided to the SwiftUI view.

To listen to changes in storyboard components, delegates are used. Delegates handle event notifications before or after the event itself has been handled by the system.

And, to listen to `StoryboardViewController` changes our `Coordinator` will have to conform to the `StoryboardViewControllerDelegate` protocol we declared earlier, as well as inherit from `NSObject` to conform to the [`NSObjectProtocol`](https://developer.apple.com/documentation/objectivec/nsobjectprotocol) protocol which cannot be declared in Swift.

Let's declare our `Coordinator` type with a constructor setting a reference to the `StoryboardView` instance whose changes it observes:
 
```Swift:Swift
struct StoryboardView: UIViewControllerRepresentable {
    // ...
    final class Coordinator: NSObject, StoryboardViewControllerDelegate {
        var parent: StoryboardView
        init(_ parent: StoryboardView) {
            self.parent = parent
        }
        // ...
    }
    // ...
}
```
It's now time to implement the 2 required methods for the `StoryboardViewControllerDelegate`:

##### 1.`didPressChangeText(_:)`
Change the content of `labelText` to the content of `inputText` in the `StoryboardViewController`:

```Swift:Swift
    func didPressChangeText(_ storyboardVC: StoryboardViewController) {
        storyboardVC.labelText.text = storyboardVC.inputText.text
        storyboardVC.inputText.text = ""
    }
```

##### 2.`didPressGoBack(_:)`
Simply dismiss the view using the environment property `presentationMode` from the `Coordinator` parent:

```Swift:Swift
    func didPressGoBack(_ storyboardVC: StoryboardViewController) {
        parent.presentationMode.wrappedValue.dismiss()
    }
```
##### Custom Coordinator Object
To provide our own **coordinator** object instead of the default implementation provided, we need to implement the [`makeCoordinator()`](https://developer.apple.com/documentation/swiftui/nsviewcontrollerrepresentable/makecoordinator()-1enxe) method in `StoryboardView`:

```Swift:Swift
struct StoryboardView: UIViewControllerRepresentable {
    // ...
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    // ...
}
```
### The StoryboardView Source Code

```Swift:StoryboardView.swift
import SwiftUI

struct StoryboardView: UIViewControllerRepresentable {
    @Environment(\.presentationMode) private var presentationMode
    final class Coordinator: NSObject, StoryboardViewControllerDelegate {
        var parent: StoryboardView
        init(_ parent: StoryboardView) {
            self.parent = parent
        }
        func didPressChangeText(_ storyboardVC: StoryboardViewController) {
            storyboardVC.labelText.text = storyboardVC.inputText.text
            storyboardVC.inputText.text = ""
        }
        func didPressGoBack(_ storyboardVC: StoryboardViewController) {
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    func makeUIViewController(context: UIViewControllerRepresentableContext<StoryboardView>) -> StoryboardViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let storyboardVC = storyboard.instantiateInitialViewController() as! StoryboardViewController
        storyboardVC.delegate = context.coordinator
        return storyboardVC
    }
    func updateUIViewController(_ uiViewController: StoryboardViewController, context: UIViewControllerRepresentableContext<StoryboardView>) {
    }
}
```
### The ContentView Source Code
Finally, let's add our `StoryboardView` to the App `ContentView`.

We use a [`Button`](https://developer.apple.com/documentation/swiftui/button) to present our `StoryboardView` in a sheet using the [`sheet(isPresented:onDismiss:content:)`](https://developer.apple.com/documentation/SwiftUI/AnyView/sheet(isPresented:onDismiss:content:)) method:

```Swift:ContentView.swift
import Foundation
import UIKit
import SwiftUI

struct ContentView: View {
    @State private var isShowingStoryboardView = false

    var body: some View {
        VStack {
        Text("Launch Storyboard View!")
            .padding()
            Button(action: {
                self.isShowingStoryboardView = true
            }) {
                Image(systemName: "play.fill")
                    .font(.system(size: 40))
            }.sheet(isPresented: self.$isShowingStoryboardView) {
                StoryboardView()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```
## The App
Launch the App, and in the content view, click on the "▶" button to open the storyboard view controller:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/bcd1aef8-45e0-ca41-e170-8f0d78cbc4d9.png)
In the storyboard view,  input some new text in the textfield:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/060bad35-165d-b314-9926-b0b34326bb50.png)
Then, click on the "Change Text!" button, and the "Initial Text" in the second `Text` element gets replaced with the new text:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/c1e1fe3a-09d1-b800-05a0-dc97d522a4b6.png)
Finally, click on the "Go Back!" button, and the sheet displaying the storyboard view gets dismissed:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/bcd1aef8-45e0-ca41-e170-8f0d78cbc4d9.png)
It works!