LoginSignup
5
3

More than 3 years have passed since last update.

SwiftUI UITextField  キャレット動作

Last updated at Posted at 2019-12-17

こんにちはフリーランスの永田です。最近は法人化の手続きを開始しました。

SwiftUI案件を1月から実施予定で、現在技術を調査中です。

今回はキャレット動作 returnButtonを押下しましたら、水平移動する対応です。

環境

Xcode 11.3
SwiftUI

SwiftUIではない場合(オリジナルです。)

キャレット動作しない場合

キャレット動作


import UIKit
import SwiftUI

struct ContentView: View {

    @State var text: String = ""

    @State var text2: String = ""

    @State var spacing: CGFloat = 0

    @State var didTap  = false

    var body: some View {
        VStack {
            HStack(alignment: .bottom, spacing: spacing) {

                SATextField(tag: 0, placeholder: "placeholder", changeHandler: { (newString) in
                    self.text = newString
                }, onCommitHandler: {
                    // write something
                })
                self.text.isEmpty == false ?
                    HorizontalLine(color: self.didTap ? Color.red : Color.black) :
                    HorizontalLine(color: self.didTap ? Color.black : Color.red)

                SATextField(tag: 1, placeholder: "placeholder2", changeHandler: { (newString) in
                    self.text2 = newString
                }, onCommitHandler: {
                    // write something
                })
                text2.isEmpty == false ?
                    HorizontalLine(color: didTap ? Color.red : Color.black) :
                    HorizontalLine(color: didTap ? Color.black : Color.red)
            }
        }.position(.init(x: UIScreen.main.bounds.width/2+50, y: UIScreen.main.bounds.height/2))
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct HorizontalLine: View {

    private var color: Color? = nil
    private var height: CGFloat = 1.0
    private var shape: HorizontalLineShape?

    init(color: Color, height: CGFloat = 1.0) {
        self.color = color
        self.height = height
    }

    var body: some View {
        HorizontalLineShape().fill(self.color!).frame(minWidth: 0, maxWidth: .infinity, minHeight: height, maxHeight: height)
    }
}

struct HorizontalLineShape: Shape {

    func path(in rect: CGRect) -> Path {

        let fill = CGRect(x: -rect.size.width, y: 0, width: rect.size.width, height: rect.size.height)
        var path = Path()
        path.addRoundedRect(in: fill, cornerSize: CGSize(width: 2, height: 2))

        return path
    }
}

class Model: ObservableObject {
    @Published var text = ""
    var placeholder = "Placeholder"
}
// check is this
// https://medium.com/@valv0/textfield-and-uiviewrepresentable-46a8d3ec48e2
struct SATextField: UIViewRepresentable {
    private let tmpView = WrappableTextField()

    //var exposed to SwiftUI object init
    var tag:Int = 0
    var placeholder:String?
    var changeHandler:((String)->Void)?
    var onCommitHandler:(()->Void)?

    func makeUIView(context: UIViewRepresentableContext<SATextField>) -> WrappableTextField {
        tmpView.tag = tag
        tmpView.delegate = tmpView
        tmpView.placeholder = placeholder
        tmpView.onCommitHandler = onCommitHandler
        tmpView.textFieldChangedHandler = changeHandler
        return tmpView
    }

    func updateUIView(_ uiView: WrappableTextField, context: UIViewRepresentableContext<SATextField>) {
        uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
        uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
    }
}

class WrappableTextField: UITextField, UITextFieldDelegate {
    var textFieldChangedHandler: ((String)->Void)?
    var onCommitHandler: (()->Void)?

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if let nextField = textField.superview?.superview?.viewWithTag(textField.tag + 1) as? UITextField {
            nextField.becomeFirstResponder()
        } else {
            textField.resignFirstResponder()
        }
        return false
    }

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if let currentValue = textField.text as NSString? {
            let proposedValue = currentValue.replacingCharacters(in: range, with: string)
            textFieldChangedHandler?(proposedValue as String)
        }
        return true
    }

    func textFieldDidEndEditing(_ textField: UITextField) {
        onCommitHandler?()
    }
}

 

参考サイト

こちらのロジックを拝借させていただきました。ロジックの部分は、今まで通りのプログラムになります。

https://medium.com/@valv0/textfield-and-uiviewrepresentable-46a8d3ec48e2

プログラムがわかる場合は
changeHandlerを追えば、すぐにわかると思います

起動時に

makeUIViewメソッドでtmpView.textFieldChangedHandler = changeHandler
を代入します。
既存のUITextFieldメソッドで実装しています。onCommitHandlerもbind処理をしていますが、
Flowは同じです。

このメソッドは文字が1文字づつ変化する事に処理が行われます。 textFieldChangedHandler?(proposedValue as String)でbindしてchangeHandlerが呼ばれています。


 func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if let currentValue = textField.text as NSString? {
            let proposedValue = currentValue.replacingCharacters(in: range, with: string)
            textFieldChangedHandler?(proposedValue as String)
        }
        return true
    }

return Buttonを押下時 これでキャレットnextField.becomeFirstResponder()
UITextFieldのキーボードを開くメソッドです。


func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if let nextField = textField.superview?.superview?.viewWithTag(textField.tag + 1) as? UITextField {
            nextField.becomeFirstResponder()
        } else {
            textField.resignFirstResponder()
        }
        return false
    }

以上、とても簡単に解説しました
貴重なお時間お読みくださいまして、誠にありがとうございます。

5
3
1

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
5
3