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