Created
June 6, 2020 08:11
-
-
Save dlgchg/3a9d89b82a844794a109d57f8b03d75c to your computer and use it in GitHub Desktop.
TextField limit input
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // MultilineTextField.swift | |
| // GipsyToDo | |
| // | |
| // Created by liwei on 2020/6/4. | |
| // Copyright © 2020 liwei. All rights reserved. | |
| // | |
| import Foundation | |
| import SwiftUI | |
| import UIKit | |
| struct MultilineTextField: View { | |
| private var placeholder: String | |
| private var onCommit: (() -> Void)? | |
| @State private var viewHeight: CGFloat = 40 //start with one line | |
| @State private var shouldShowPlaceholder = false | |
| @Binding private var text: String | |
| @Binding private var showKeyBoard: Bool | |
| private var internalText: Binding<String> { | |
| Binding<String>(get: { self.text } ) { | |
| self.text = $0 | |
| self.shouldShowPlaceholder = $0.isEmpty | |
| } | |
| } | |
| var body: some View { | |
| UITextViewWrapper(text: self.internalText, showKeyBoard: $showKeyBoard, calculatedHeight: $viewHeight, onDone: onCommit) | |
| .frame(minHeight: viewHeight, maxHeight: viewHeight) | |
| .background(placeholderView, alignment: .topLeading) | |
| } | |
| var placeholderView: some View { | |
| Group { | |
| if shouldShowPlaceholder { | |
| Text(placeholder).foregroundColor(.gray) | |
| .padding(.leading, 4) | |
| .padding(.top, 8) | |
| } | |
| } | |
| } | |
| init (_ placeholder: String = "", text: Binding<String>, showKeyBoard: Binding<Bool>, onCommit: (() -> Void)? = nil) { | |
| self.placeholder = placeholder | |
| self.onCommit = onCommit | |
| self._text = text | |
| self._showKeyBoard = showKeyBoard | |
| self._shouldShowPlaceholder = State<Bool>(initialValue: self.text.isEmpty) | |
| } | |
| } | |
| private struct UITextViewWrapper: UIViewRepresentable { | |
| typealias UIViewType = UITextView | |
| @Binding var text: String | |
| @Binding var showKeyBoard: Bool | |
| @Binding var calculatedHeight: CGFloat | |
| var onDone: (() -> Void)? | |
| func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView { | |
| let textField = UITextView() | |
| textField.delegate = context.coordinator | |
| textField.isEditable = true | |
| textField.font = UIFont.preferredFont(forTextStyle: .body) | |
| textField.isSelectable = true | |
| textField.isUserInteractionEnabled = true | |
| textField.isScrollEnabled = false | |
| textField.backgroundColor = UIColor.clear | |
| if nil != onDone { | |
| textField.returnKeyType = .done | |
| } | |
| textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) | |
| return textField | |
| } | |
| func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) { | |
| if uiView.text != self.text { | |
| uiView.text = self.text | |
| } | |
| if self.showKeyBoard { | |
| uiView.becomeFirstResponder() | |
| } else { | |
| uiView.resignFirstResponder() | |
| } | |
| // if uiView.window != nil, !uiView.isFirstResponder { | |
| // uiView.becomeFirstResponder() | |
| // } | |
| UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight) | |
| } | |
| private static func recalculateHeight(view: UIView, result: Binding<CGFloat>) { | |
| let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude)) | |
| if result.wrappedValue != newSize.height { | |
| DispatchQueue.main.async { | |
| result.wrappedValue = newSize.height // call in next render cycle. | |
| } | |
| } | |
| } | |
| func makeCoordinator() -> Coordinator { | |
| return Coordinator(text: $text, showKeyBoard: $showKeyBoard, height: $calculatedHeight, onDone: onDone) | |
| } | |
| final class Coordinator: NSObject, UITextViewDelegate { | |
| var text: Binding<String> | |
| var showKeyBoard: Binding<Bool> | |
| var calculatedHeight: Binding<CGFloat> | |
| var onDone: (() -> Void)? | |
| init(text: Binding<String>,showKeyBoard: Binding<Bool>, height: Binding<CGFloat>, onDone: (() -> Void)? = nil) { | |
| self.text = text | |
| self.showKeyBoard = showKeyBoard | |
| self.calculatedHeight = height | |
| self.onDone = onDone | |
| } | |
| func textViewDidChange(_ uiView: UITextView) { | |
| text.wrappedValue = uiView.text | |
| UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight) | |
| } | |
| func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { | |
| if let onDone = self.onDone, text == "\n" { | |
| textView.resignFirstResponder() | |
| onDone() | |
| return false | |
| } | |
| return true | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment