10
6

More than 1 year has passed since last update.

SwiftUIで困ったら「UIViewRepresentable」

Last updated at Posted at 2022-04-18

はじめに

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

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

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

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

UILabel

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

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

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

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

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

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

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

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

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

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のコンポーネントはまだまだあります。
個人開発などで使った時に追加していこうと思います。

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6