SwiftUIで困ったら「UIViewRepresentable」

# はじめに
2019年6月3日にSwiftUIが発表されてから2年と10ヶ月経ちました。
しかし、未だにSwiftUIにはできない事が多くあります。
そうした理由からUIKitからの移行が進んでいません。

「どっちか１つなんて選べないよ〜」

SwiftUIにできない事はUIKitにやってもらえば良くない？という事です。
そこで登場するのが「UIViewRepresentable」です。

今回はUIViewRepresentableを使ってUIKitをSwiftUIで使えるようにしていこうと思います。

# UILabel
```Swift
struct LabelView: UIViewRepresentable {
    let text: String
    func makeUIView(context: Context) -> UILabel {
        let uiLabel = UILabel()
        uiLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)
        uiLabel.setContentCompressionResistancePriority(.required, for: .vertical)
        return uiLabel
    }
    func updateUIView(_ uiLabel: UILabel, context: Context) {
        uiLabel.text = text
    }
}
```

# UIImage
```Swift
struct ImageView: UIViewRepresentable {
    let imageName: String
    func makeUIView(context: Context) -> UIView {
        let imageView = UIImageView(image: UIImage(named: "mini_dot")!)
        imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        imageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
        return imageView
    }
    func updateUIView(_ uiImage: UIView, context: Context) {
    }
}
```

# UITextField
```Swift
struct TextFieldView: UIViewRepresentable {
    let placeholder: String
    @Binding var text: String
    let onEditingChanged: (Bool) -> Void
    let onCommit: () -> Void
    let textField = UITextField()
    init(placeholder: String, text: Binding<String>, onEditingChanged: @escaping (Bool) -> Void = { _ in }, onCommit: @escaping () -> Void = {}) {
        self.placeholder = placeholder
        self._text = text
        self.onEditingChanged = onEditingChanged
        self.onCommit = onCommit
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }

    func makeUIView(context: Context) -> UITextField {
        textField.placeholder = placeholder
        return textField
    }
    func updateUIView(_ uiView: UITextField, context: Context) {
    }
    class Coordinator: NSObject {
        var uiTextField: TextFieldView
        init(_ uiTextField: TextFieldView) {
            self.uiTextField = uiTextField
            super.init()
            uiTextField.textField.addTarget(self, action: #selector(textFieldEditingDidBegin(_:)), for: .editingDidBegin)
            uiTextField.textField.addTarget(self, action: #selector(textFieldEditingDidEnd(_:)), for: .editingDidEnd)
            uiTextField.textField.addTarget(self, action: #selector(textFieldEditingChanged(_:)), for: .editingChanged)
            uiTextField.textField.addTarget(self, action: #selector(textFieldEditingDidEndOnExit(_:)), for: .editingDidEndOnExit)
        }
        @objc  func textFieldEditingDidBegin(_ textField: UITextField) {
            uiTextField.onEditingChanged(true)
        }
        @objc  func textFieldEditingDidEnd(_ textField: UITextField) {
            uiTextField.onEditingChanged(false)
        }
        @objc  func textFieldEditingChanged(_ textField: UITextField) {
            uiTextField.text = textField.text ?? ""
        }
        @objc  func textFieldEditingDidEndOnExit(_ textField: UITextField) {
            uiTextField.onCommit()
        }
    }
}
```

# UISwitch
```Swift
struct SwitchView: UIViewRepresentable {
    @Binding var isOn: Bool
    func makeUIView(context: Context) -> UISwitch {
        let uiSwitch = UISwitch()
        uiSwitch.addTarget(context.coordinator, action: #selector(Coordinator.changed(_:)), for: .valueChanged)
        return uiSwitch
    }
    func updateUIView(_ uiSwitch: UISwitch, context: Context) {
        uiSwitch.isOn = isOn
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(isOn: $isOn)
    }
    class Coordinator: NSObject {
        let isOn: Binding<Bool>
        init(isOn: Binding<Bool>) {
            self.isOn = isOn
        }
        @objc func changed(_ sender: UISwitch) {
            self.isOn.wrappedValue = sender.isOn
        }
    }
}
```

# UISlider
```Swift
struct SliderView: UIViewRepresentable {
    @Binding var value: Float
    func makeUIView(context: Context) -> UISlider {
        let uiSlider = UISlider()
        uiSlider.addTarget(context.coordinator, action: #selector(Coordinator.changed(_:)), for: .valueChanged)
        return uiSlider
    }
    func updateUIView(_ uiSlider: UISlider, context: Context) {
        uiSlider.value = value
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(value: $value)
    }
    class Coordinator: NSObject {
        let value: Binding<Float>
        init(value: Binding<Float>) {
            self.value = value
        }
        @objc func changed(_ sender: UISlider) {
            self.value.wrappedValue = sender.value
        }
    }
}
```

# UIButton
```Swift
struct ButtonView: UIViewRepresentable {
    func makeUIView(context: Context) -> UIButton {
        let uiButton = UIButton()
        uiButton.setTitle("ボタン", for: .normal)
        uiButton.setTitleColor(.black, for: .normal)
        uiButton.addTarget(context.coordinator, action: #selector(Coordinator.ButtonAction(sender:)), for: .touchUpInside)
        return uiButton
    }
    func updateUIView(_ uiButton: UIButton, context: Context) {
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(uiButton: self)
    }
    class Coordinator {
        var uiButton: ButtonView
        init(uiButton: ButtonView) {
            self.uiButton = uiButton
        }
        @objc func ButtonAction(sender: UIButton) {
            print("ボタンです")
        }
    }
}
```

# UITableView
```Swift
class Cell: UITableViewCell {
    var host: UIHostingController<AnyView>?
}

struct TableView: UIViewRepresentable {
    var rows: [String]
    var cellHeight: CGFloat
    var cellColor: Color
    func makeUIView(context: Context) -> UITableView {
        let uiTableView = UITableView()
        uiTableView.translatesAutoresizingMaskIntoConstraints = false
        uiTableView.dataSource = context.coordinator
        uiTableView.delegate = context.coordinator
        uiTableView.register(Cell.self, forCellReuseIdentifier: "Cell")
        return uiTableView
    }
    func updateUIView(_ uiTableView: UITableView, context: Context) {
    }
    func makeCoordinator() -> Coordinator {
        Coordinator(rows: rows, cellHeight: cellHeight, cellColor: cellColor)
    }
    class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
        var rows: [String]
        var cellHeight: CGFloat
        var cellColor: Color
        init(rows: [String], cellHeight: CGFloat, cellColor: Color) {
            self.rows = rows
            self.cellHeight = cellHeight
            self.cellColor = cellColor
        }
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            self.rows.count
        }
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
            let view = Text(rows[indexPath.row]).frame(height: cellHeight).background(cellColor)
            if tableViewCell.host == nil {
                let controller = UIHostingController(rootView: AnyView(view))
                tableViewCell.host = controller

                let tableCellViewContent = controller.view!
                tableCellViewContent.translatesAutoresizingMaskIntoConstraints = false
                tableViewCell.contentView.addSubview(tableCellViewContent)
                tableCellViewContent.topAnchor.constraint(equalTo: tableViewCell.contentView.topAnchor).isActive = true
                tableCellViewContent.leftAnchor.constraint(equalTo: tableViewCell.contentView.leftAnchor).isActive = true
                tableCellViewContent.bottomAnchor.constraint(equalTo: tableViewCell.contentView.bottomAnchor).isActive = true
                tableCellViewContent.rightAnchor.constraint(equalTo: tableViewCell.contentView.rightAnchor).isActive = true
            } else {
                tableViewCell.host?.rootView = AnyView(view)
            }
            tableViewCell.setNeedsLayout()
            return tableViewCell
        }
    }
}
```

# UISegmentedControl
```Swift
struct SegmentedView: UIViewRepresentable {
    var item: [Any]?
    @Binding var index: Int
    func makeUIView(context: Context) -> UISegmentedControl {
        let uiSegmentedControl = UISegmentedControl(items: item)
        uiSegmentedControl.addTarget(context.coordinator, action: #selector(Coordinator.changed(_:)), for: .valueChanged)
        return uiSegmentedControl
    }
    func updateUIView(_ uiSegmentedControl: UISegmentedControl, context: Context) {
        uiSegmentedControl.selectedSegmentIndex = index
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(index: $index)
    }
    class Coordinator: NSObject {
        let index: Binding<Int>
        init(index: Binding<Int>) {
            self.index = index
        }
        @objc func changed(_ sender: UISegmentedControl) {
            self.index.wrappedValue = sender.selectedSegmentIndex
        }
    }
}
```

# UIStepper
```Swift
struct StepperView: UIViewRepresentable {
    @Binding var value: Double
    func makeUIView(context: Context) -> UIStepper {
        let uiStepper = UIStepper()
        uiStepper.addTarget(context.coordinator, action: #selector(Coordinator.changed(_:)), for: .valueChanged)
        return uiStepper
    }
    func updateUIView(_ uiStepper: UIStepper, context: Context) {
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(value: $value)
    }
    class Coordinator: NSObject {
        let value: Binding<Double>
        init(value: Binding<Double>) {
            self.value = value
        }
        @objc func changed(_ sender: UIStepper) {
            self.value.wrappedValue = sender.value
        }
    }
}
```

# UIDatePicker
```Swift
struct DatePickerView: UIViewRepresentable {
    @Binding var date: Date
    func makeUIView(context: Context) -> UIDatePicker {
        let uiDatePicker = UIDatePicker()
        uiDatePicker.datePickerMode = .date
        uiDatePicker.addTarget(context.coordinator, action: #selector(Coordinator.changed(_:)), for: .valueChanged)
        return uiDatePicker
    }
    func updateUIView(_ uiDatePicker: UIDatePicker, context: Context) {
        uiDatePicker.date = date
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(date: $date)
    }
    class Coordinator: NSObject {
        private let date: Binding<Date>
        private let range: ClosedRange<Date>?
        private let minuteInterval: Int
        init(date: Binding<Date>, in range: ClosedRange<Date>? = nil, minuteInterval: Int = 1) {
            self.date = date
            self.range = range
            self.minuteInterval = minuteInterval
        }
        @objc func changed(_ sender: UIDatePicker) {
            self.date.wrappedValue = sender.date
        }
    }
}
```

# おわり
UIKitのコンポーネントはまだまだあります。
個人開発などで使った時に追加していこうと思います。