SwiftUI

SwiftUI动态改变文字输入框的行数

在App开发过程中,经常会遇到类似下面的需求,随着输入文字数的增加,需要我们动态调整文本框的行数,以便可以把入力文字的都显示出来。所以我们必须要监听到输入框文字的输入的事件。这样的话,SwiftUI的输入框控件TextField是不合适的,因为SwiftUI的TextField是单行的,并且没法监听到文字输入的事件,所以我们这里采用了UIKit的UITextView。至少在目前阶段,SwiftUI是很难独立开发一款功能强大的App,很多情况下还是需要和UIkit一起合作,借助UIlkit完整的体系,二者相互嵌套形成混合开发,目前主要是在SwiftUI中使用UIkit。


1.要在SwiftUI中使用UITextView, 我们需要用到UIViewRepresentable, 对UITextView进行包装。
// 定义SwiftUI的ChatTextView,并且实现UIViewRepresentable
struct ChatTextView: UIViewRepresentable {
    
    @Binding var myTextView: UITextView
    // UITextView高度行数
    @Binding var contentHeight: CGFloat

    // 在makeUIView方法中,创建UITextView
    func makeUIView(context: Context) -> UITextView {
        let view = UITextView()
        // 是否可以滚动
        view.isScrollEnabled = true
        // 是否可以编辑
        view.isEditable = true
        // 输入文字颜色
        view.textColor = .black
        // 输入文字字号
        view.font = UIFont.systemFont(ofSize: 16);
        // 输入框圆角
        view.layer.cornerRadius = 6
        // 输入框背景颜色
        view.backgroundColor = UIColor.init(red: 255 / 255, green: 255 / 255, blue: 255 / 255, alpha: 1)
       // 指定输入框各种事件的delegate代理
        view.delegate = context.coordinator
       // 输入框弹出键盘类型
        view.keyboardType = .default
       // 输入框边框宽度
        view.layer.borderWidth = 1
       // 输入框边框颜色
        view.layer.borderColor = CGColor.init(srgbRed: 128 / 255, green: 128 / 255, blue: 128 / 255, alpha: 0.2)
        return view
    }

    // 没有需要的话,不用特别处理。
    func updateUIView(_ uiView: UITextView, context: Context) {
    }

    func makeCoordinator() -> ChatTextView.Coordinator {
        Coordinator(self)
    }
   
    // 内部类,指定ChatTextView为Coordinator的成员变量,这样Coordinator成了ChatTextView和UITextView之间的桥梁,形成了类似桥接的效果。
    class Coordinator: NSObject, UITextViewDelegate {

        // 指定ChatTextView为Coordinator的成员变量
        var control: ChatTextView

        init(_ control: ChatTextView) {
            self.control = control
        }
       
        // 键盘弹出事件,在这里可以控制View视图的offset,避免视图被弹出键盘遮挡。
        func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
            self.control.myTextView = textView
            NotificationCenter.default.post(name: .editBegin, object: nil, userInfo: nil)
            return true
        }

        // 键盘关闭事件,在这里可以控制View视图的offset,键盘关闭时,复原视图位置。
        func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
            NotificationCenter.default.post(name: .editEnd, object: nil, userInfo: nil)
            textView.endEditing(true)
            return true
        }

        // ※键盘输入文字事件,在这里面可以获得UITextView当前的行高,我们要利用这个行高来改变ChatTextView的行高。
        func textViewDidChange(_ textView: UITextView) {
            // 设定最小行高
            if textView.contentSize.height == 36 {
                self.control.contentHeight = 45
            // 限制最大行高
            } else if textView.contentSize.height >= 74 {
                self.control.contentHeight = 74
            } else {
                // 通过桥接可以把行高传递出去
                self.control.contentHeight = textView.contentSize.height
            }
        }
    }
}

2.在别的View中,可以使用我们上面定义好的ChatTextView控件, 并且根据输入文字数,动态调整显示的行数了。
import SwiftUI

struct MessageHistoryView: View {

    @State var myTextView: UITextView = UITextView.init()
    @State var contentHeight: CGFloat = 45

    var body: some View {
        // binding contentHeight值就是实时得到的行高
       // 利用frame的height属性来调整ChatTextView的高度
        ChatTextView(myTextView: self.$myTextView, contentHeight: self.$contentHeight).frame(width: UIScreen.main.bounds.width - 90, height: self.contentHeight)
        
    }
}