SwiftUI기반 앱을 만들면서 UIViewRepresentable를 써줘야하는 실질적 상황이 생겼다.
textField에서는 키보드 말고도 다양한 형태의 inputView에서 입력값을 받아줄 수 있어야한다. 나는 커스텀하여 만든 picker에서 선택한 값이 textField의 입력값으로 반영되는 기능을 구현하고싶었다. 하지만 SwiftUI에서는 UIKit에서 처럼 UITextField에서의 inputView를 따로 커스텀해줄 수 없었기에 UIViewRepresentable를 활용해서 UIKit 뷰를 만들어주어야했다.
UIViewRepresentable의 개념적 내용이 궁금하다면 아래 두 개의 포스팅이 도움이 될 것 이다.
https://heidi-dev.tistory.com/6
SwiftUI에서 UIKit 사용하기 UIViewRepresentable (1) | TextField에서 clear button 사용하고 싶다면
SwiftUI 기반인 앱을 구현하면서 UIKit에서는 지원하지만 SwiftUI에서는 지원하지 않는 뷰들이 아직 존재한다는 것을 알게 되었다. 그럴 때 UIKit에서 제공하는 뷰를 SwiftUI에서 사용할 수 있도록 래핑
heidi-dev.tistory.com
https://heidi-dev.tistory.com/7
SwiftUI에서 UIKit 사용하기 UIViewRepresentable(2) | @Binding 이란? Coordinator 란?
앞서 SwiftUI에서 UIKit를 사용하기 위해 UIViewRepresentable 프로토콜을 사용하는 방법을 간단히 알아보았다. 이제 여기서 궁금한 것은 SwiftUI와 UIKit 간에 데이터를 주고받고 변화를 감지할 수 있는가이
heidi-dev.tistory.com
# 🥨 기본적인 UITextField 생성
SwiftUI에서 입력값을 받아주어야하기 때문에 @State 로 변수를 설정해주었고 @Binding property 로 구조체에 값을 넘겨주었다. picker에 넣어주고 싶은 데이터들은 프로퍼티로 받아준다.
import SwiftUI
struct UIVIewRepresentablePracticeView: View {
@State var picked : String = ""
var body: some View {
VStack{
CustomPickerTextField(
dataArrays :Array(0...100).map{String($0)},
bindingString: self.$picked
)
}
.padding()
}
}
struct CustomPickerTextField : UIViewRepresentable {
private let textField = UITextField()
public var dataArrays : [String]
public var placeholder: String = "입력하기"
@Binding public var bindingString: String
func makeUIView(context: UIViewRepresentableContext<CustomPickerTextField>) -> UITextField {
textField.placeholder = placeholder
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomPickerTextField>) {
}
}
# 🥨 키보드 대신 custom picker를 inputView로서 띄우기
UITextField의 inputView로서 UIPickerView를 지정해주고, UITextField의 inputAccessoryView로서 UIToolbar를 지정해주면 텍스트필드를 눌렀을 때 아까 키보드가 나타난 것과는 다른 뷰를 확인할 수 있을것이다.
struct CustomPickerTextField : UIViewRepresentable {
private let textField = UITextField()
private let picker = UIPickerView()
private let toolbar = UIToolbar()
public var dataArrays : [String]
public var placeholder: String = "입력하기"
@Binding public var bindingString: String
func makeUIView(context: UIViewRepresentableContext<CustomPickerTextField>) -> UITextField {
textField.placeholder = placeholder
textField.inputView = picker
//툴바
toolbar.sizeToFit()
textField.inputAccessoryView = toolbar
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomPickerTextField>) {
}
}
# 🥨 Picker에서 띄워줄 데이터 지정 및 뷰 생성
Coordinator 클래스에서 UIPickerViewDataSource, UIPickerViewDelegate 를 채택하고 내부메서드를 활용해서 Picker에서 띄워줄 데이터를 지정하고 뷰를 생성해준다. 또한, 툴바의 Done 버튼을 만들어주고 Done 버튼이 눌릴 때 실행할 로직을 설정해준다.
struct CustomPickerTextField : UIViewRepresentable {
private let textField = UITextField()
private let picker = UIPickerView()
private let toolbar = UIToolbar()
private let helper = Helper()
public var dataArrays : [String]
public var placeholder: String = "입력하기"
@Binding public var bindingString: String
class Helper {
public var onDoneButtonTapped: (() -> Void)?
@objc func doneButtonTapped() {
onDoneButtonTapped?()
}
}
//makeCoordinator()
func makeCoordinator() -> CustomPickerTextField.Coordinator {
CustomPickerTextField.Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<CustomPickerTextField>) -> UITextField {
let defaultIndex : Int = 0
picker.dataSource = context.coordinator
picker.delegate = context.coordinator
picker.selectRow(defaultIndex, inComponent: 0, animated: true)
textField.placeholder = placeholder
textField.inputView = picker
//툴바
toolbar.sizeToFit()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: helper, action: #selector(helper.doneButtonTapped))
toolbar.setItems([flexibleSpace, doneButton], animated: true)
textField.inputAccessoryView = toolbar
helper.onDoneButtonTapped = {
textField.resignFirstResponder()
}
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomPickerTextField>) {
}
class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
var parent: CustomPickerTextField
init(_ pickerView: CustomPickerTextField) {
self.parent = pickerView
}
//Number Of Components
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
//Number Of Rows In Component
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return parent.dataArrays.count
}
//Width for component
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
return UIScreen.main.bounds.width
}
//Row height
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
return 40
}
//View for Row
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let view = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width/4, height: 60))
let pickerLabel = UILabel(frame: view.bounds)
pickerLabel.text = parent.dataArrays[row]
pickerLabel.adjustsFontSizeToFitWidth = true
pickerLabel.textAlignment = .center
pickerLabel.lineBreakMode = .byWordWrapping
pickerLabel.numberOfLines = 0
view.addSubview(pickerLabel)
view.clipsToBounds = true
return view
}
}
}
# 🥨 사용자 입력값 UITextField에 반영 및 Swiftui와 UIKit간에 데이터 주고받기
사용자가 선택한 값을 UITextField에 반영되게 해주고, Swiftui와 UIViewRepresentable로 래핑해준 UIKit간에 데이터를 전달하기위한 코드를 추가해야한다
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomPickerTextField>) {
//🌸 UITextField에 사용자가 입력한 값이 반영 될 수 있도록한다.
uiView.text = bindingString
}
//🌸 사용자가 picker에서 입력값 선택했을 때마다 호출
// bindingString값을 사용자가 입력한 값으로 바인딩 시킨다. (UIKit -> SwiftUI 방향으로의 데이터 전달)
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
parent.bindingString = parent.dataArrays[pickerView.selectedRow(inComponent: component)]
}
SwiftUI에서 변화된 입력값에 대한 데이터를 제대로 받고 있는지 알고싶다면 아래 코드를 활용해서 시뮬레이터를 실행시키고 xcode 터미널에서 확인해보면 데이터를 잘 받고 있는것을 확인할 수 있다 :)
import SwiftUI
struct UIVIewRepresentablePracticeView: View {
@State var picked : String = ""
var body: some View {
VStack{
CustomPickerTextField(
dataArrays :Array(0...100).map{String($0)},
bindingString: self.$picked
)
Text("@State 확인")
.onTapGesture {
print("@State var picked : ", picked)
}
}
.padding()
}
}
'SwiftUI' 카테고리의 다른 글
[SwiftUI] @StateObject 와 @ObservedObject (0) | 2023.06.21 |
---|---|
[SwiftUI] UIViewControllerRepresentable프로토콜로 갤러리에서 이미지 선택하기 (0) | 2023.06.20 |
SwiftUI에서 UIKit 사용하기 UIViewRepresentable(2) | @Binding 이란? Coordinator 란? (0) | 2023.06.09 |
SwiftUI에서 UIKit 사용하기 UIViewRepresentable (1) | TextField에서 clear button 사용하고 싶다면 (0) | 2023.06.08 |
SwiftUI 다양한 토글 형태를 만들어 주기 위한 ToggleStyle 프로토콜 (0) | 2023.06.06 |