I found this code on the web :
struct CustomTextField: UIViewRepresentable {
#Binding var text: String
#State var placeholder: String
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.borderStyle = .roundedRect
textField.placeholder = placeholder
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.keyboardType = .URL
textField.frame = CGRect(x: 0, y: 0, width: 20.00, height: 10)
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ view: UITextField, context: Context) {
view.text = text
}
}
extension CustomTextField {
class Coordinator: NSObject, UITextFieldDelegate {
#Binding var text: String
init(text: Binding<String>) {
_text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
DispatchQueue.main.async {
self.text = textField.text ?? ""
}
}
}
}
The code works absolutely fine. The problem with this is that I am not able to find a suitable way to increase the height of this. As you can see, I tried to use a CGRect as the frame, to no effect. How can I change the size (particularly height in my specific scenario) of this custom UIViewRepresentable?
Just the same you would do with any other SwiftUI view:
CustomTextField(text: $text, placeholder: "")
// constant
.frame(height: 100)
// fill available height
.frame(maxHeight: .infinity)
If you wanna make it respect intrinsicContentSize, check out this answer
Related
Repost question from this Adjust View up with Keyboard show in SwiftUI 3.0 iOS15.
SwiftUI keyboard avoidance won't show the whole textfield including the overlay.
I already tried a lot different ways from googling.
Does anyone have any solution for this?
struct ContentView: View {
#State var text: String = ""
var body: some View {
ScrollView {
VStack {
Spacer(minLength: 600)
TextField("Placeholder", text: $text)
.textFieldStyle(CustomTextFieldStyle())
}
}
}
}
struct CustomTextFieldStyle: TextFieldStyle {
func _body(configuration: TextField<Self._Label>) -> some View {
configuration
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.red, lineWidth: 5)
)
}
}
You can write the custom UITextFeild, in which the intrinsicContentSize will be overridden.
Example:
final class _UITextField: UITextField {
override var intrinsicContentSize: CGSize {
CGSize(width: UIView.noIntrinsicMetric, height: 56)
}
}
Then, you can write your own implementation of TextField, using UIViewRepresentable protocol and UITextFieldDelegate:
struct _TextField: UIViewRepresentable {
private let title: String?
#Binding var text: String
let textField = _UITextField()
init(
_ title: String?,
text: Binding<String>
) {
self.title = title
self._text = text
}
func makeCoordinator() -> _TextFieldCoordinator {
_TextFieldCoordinator(self)
}
func makeUIView(context: Context) -> _UITextField {
textField.placeholder = title
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: _UITextField, context: Context) {}
}
final class _TextFieldCoordinator: NSObject, UITextFieldDelegate {
private let control: _TextField
init(_ control: _TextField) {
self.control = control
super.init()
control.textField.addTarget(self, action: #selector(textFieldEditingChanged), for: .editingChanged)
}
#objc private func textFieldEditingChanged(_ textField: UITextField) {
control.text = textField.text ?? ""
}
}
I created an NSViewRepresentable structure that manages the state of an NSTextView. The view, by default, only has one line and I'd like to extend that to the edge of the window.
The view is defined as such:
struct Editor: View, NSViewRepresentable {
#Binding var text: String
func makeNSView(context: Context) -> NSTextView {
let view = NSTextView()
view.font = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular)
return view
}
func updateNSView(_ view: NSTextView, context: Context) {
let attributedText = NSMutableAttributedString(string: text)
view.textStorage?.setAttributedString(attributedText)
}
}
It currently looks like this, it adds lines as you go. Is there a way for the darker text area to be expanded to the bottom of the window automatically?
You can use NSTextView.scrollableTextView() to do this:
struct ContentView : View {
#State var text = ""
var body: some View {
VStack {
Editor(text: $text)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct Editor: View, NSViewRepresentable {
#Binding var text: String
func makeNSView(context: Context) -> NSView {
let scrollView = NSTextView.scrollableTextView()
guard let textView = scrollView.documentView as? NSTextView else {
return scrollView
}
textView.font = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular)
context.coordinator.textView = textView
return scrollView
}
func updateNSView(_ view: NSView, context: Context) {
let attributedText = NSMutableAttributedString(string: text)
context.coordinator.textView?.textStorage?.setAttributedString(attributedText)
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
class Coordinator {
var textView : NSTextView?
}
}
Would like to have the toolbar show all the time.
Any help is greatly appreciated as this is a real drag for the user experience.
I've added a toolbar to the keyboard for the TextView as shown below.
However the toolbar only shows after the app has run once. Meaning the toolbar does not show the first time the app is run. the app works every time after the initial load.
This is on IOS 14.3, Xcode 12.3, Swift 5, iMessage extension app. Fails on simulator or real device.
struct CustomTextEditor: UIViewRepresentable {
#Binding var text: String
private var returnType: UIReturnKeyType
private var keyType: UIKeyboardType
private var displayDoneBar: Bool
private var commitHandler: (()->Void)?
init(text: Binding<String>,
returnType: UIReturnKeyType = .done,
keyboardType: UIKeyboardType,
displayDoneBar: Bool,
onCommit: (()->Void)?) {
self._text = text
self.returnType = returnType
self.keyType = keyboardType
self.displayDoneBar = displayDoneBar
self.commitHandler = onCommit
}
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.keyboardType = keyType
textView.returnKeyType = returnType
textView.backgroundColor = .clear
textView.font = UIFont.systemFont(ofSize: 20, weight: .regular)
textView.isEditable = true
textView.delegate = context.coordinator
if self.displayDoneBar {
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace,
target: self,
action: nil)
let doneButton = UIBarButtonItem(title: "Close Keyboard",
style: .done,
target: self,
action: #selector(textView.doneButtonPressed(button:)))
let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 300, height: 50))
toolBar.items = [flexibleSpace, doneButton, flexibleSpace]
toolBar.setItems([flexibleSpace, doneButton, flexibleSpace], animated: true)
toolBar.sizeToFit()
textView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
textView.translatesAutoresizingMaskIntoConstraints = true
textView.inputAccessoryView = toolBar
}
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
textView.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: CustomTextEditor
init(_ textView: CustomTextEditor) {
self.parent = textView
}
func textViewDidChange(_ textView: UITextView) {
self.parent.$text.wrappedValue = textView.text
}
func textViewDidEndEditing(_ textView: UITextView) {
self.parent.$text.wrappedValue = textView.text
parent.commitHandler?()
}
}
}
extension UITextView {
#objc func doneButtonPressed(button:UIBarButtonItem) -> Void {
self.resignFirstResponder()
}
}
This is how it's called...
import SwiftUI
final class ContentViewHostController: UIHostingController<ContentView> {
weak var myWindow: UIWindow?
init() {
super.init(rootView: ContentView())
}
required init?(coder: NSCoder) {
super.init(coder: coder, rootView: ContentView())
}
}
let kTextColor = Color(hex: "3E484F")
let kOverlayRadius: CGFloat = 10
let kOverlayWidth: CGFloat = 2
let kOverlayColor = kTextColor
struct ContentView: View {
#State var text = ""
var body: some View {
VStack {
Spacer()
CustomTextEditor(text: $text, returnType: .default, keyboardType: .default, displayDoneBar: true, onCommit: nil)
.foregroundColor(kTextColor)
.overlay(
RoundedRectangle(cornerRadius: kOverlayRadius)
.stroke(kOverlayColor, lineWidth: kOverlayWidth)
)
.frame(width: 200, height: 100, alignment: .center)
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
From MessagesViewController...
override func willBecomeActive(with conversation: MSConversation) {
let childViewCtrl = ContentViewHostController()
childViewCtrl.view.layoutIfNeeded() // avoids snapshot warning?
if let window = self.view.window {
childViewCtrl.myWindow = window
window.rootViewController = childViewCtrl
}
}
I try to make custom textfield so I give it #Binding to response to the text value as showing below, The problem is when I try to detect the change of text its just response in the "Preview", But when run the app on the "Simulator" it doesn't response, I tried many different ways to solve this problem but nothing is work.
import SwiftUI
struct MyTextField: UIViewRepresentable {
typealias UIViewType = UITextField
#Binding var becomeFirstResponder: Bool
#Binding var text: String
var placeholder = ""
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.widthAnchor.constraint(equalToConstant: 320).isActive = true
textField.textColor = UIColor.systemBlue
textField.font = UIFont.boldSystemFont(ofSize: 22)
textField.textAlignment = .left
textField.keyboardType = .default
textField.minimumFontSize = 13
textField.adjustsFontSizeToFitWidth = true
textField.text = self._text.wrappedValue
textField.placeholder = self.placeholder
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ textField: UITextField, context: Context) {
if self.becomeFirstResponder {
DispatchQueue.main.async {
textField.becomeFirstResponder()
self.becomeFirstResponder = false
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: MyTextField
init(parent: MyTextField) {
self.parent = parent
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text ?? ""
guard let stringRange = Range(range, in: currentText) else {
return false
}
let updateText = currentText.replacingCharacters(in: stringRange, with: string)
return updateText.count < 20
}
}
}
struct TextFieldFirstResponder: View {
#State private var becomeFirstResponder = false
#State private var text = "LLL"
private var placeholder = "Untitled"
var body: some View {
VStack {
ZStack(alignment: .trailing) {
MyTextField(becomeFirstResponder: self.$becomeFirstResponder, text: self.$text, placeholder: self.placeholder)
.frame(width: 343, height: 56, alignment: .leading)
.padding(EdgeInsets(top: 27, leading: 13, bottom: 0, trailing: 0))
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color(UIColor.secondarySystemBackground))
.frame(width: 342, height: 56, alignment: .center)
)
.onAppear {
self.becomeFirstResponder = true
}
}
Text("\(self.$text.wrappedValue)") // <------ Do not read the "text"
}
}
}
struct TextFieldFirstResponder_Previews: PreviewProvider {
static var previews: some View {
TextFieldFirstResponder()
}
}
You don't need to use $ here, read property directly
Text(self.text) // <------ Do not use $
and I assume you wanted to update it via binding
let updateText = currentText.replacingCharacters(in: stringRange, with: string)
self.parent.text = updateText // << here !!
return updateText.count < 20
Tested & worked with Xcode 12.1 / iOS 14.1
When I click the return button on the keyboard, the application freezes and never becomes responsive again. I am using a UIViewRepresentable that returns a UITextField because I am unable to create an outline on the text using SwiftUI. I'm running this on Xcode beta 5 on and iPhone XR simulator.
import SwiftUI
import UIKit
struct CustomTextField: View {
#State var text: String
#State var pos: CGPoint
var body: some View {
StrokeTextLabel(text: text)
// .frame(width: UIScreen.main.bounds.width - 16, height: 40, alignment: .center)
.position(pos)
.gesture(dragGesture)
}
var dragGesture : some Gesture {
DragGesture()
.onChanged { value in
self.pos = value.location
print(self.pos)
}
}
}
struct StrokeTextLabel: UIViewRepresentable {
var text: String
func makeUIView(context: Context) -> UITextField {
let attributedStringParagraphStyle = NSMutableParagraphStyle()
attributedStringParagraphStyle.alignment = NSTextAlignment.center
let attributedString = NSAttributedString(
string: text,
attributes:[
NSAttributedString.Key.strokeColor : UIColor.white,
NSAttributedString.Key.foregroundColor : UIColor.black,
NSAttributedString.Key.strokeWidth : -4.0,
NSAttributedString.Key.font: UIFont(name: "impact", size: 50.0)!
]
)
let strokeLabel = UITextField(frame: CGRect.zero)
strokeLabel.attributedText = attributedString
strokeLabel.backgroundColor = UIColor.clear
strokeLabel.sizeToFit()
strokeLabel.center = CGPoint.init(x: 0.0, y: 0.0)
strokeLabel.sizeToFit()
return strokeLabel
}
func updateUIView(_ uiView: UITextField, context: Context) {}
}
#if DEBUG
struct CustomTextField_Previews: PreviewProvider {
static var previews: some View {
CustomTextField(text: "test text", pos: CGPoint(x: 300, y: 500))
}
}
#endif
I expect the application to be close the keyboard when the return button is clicked but instead the app freezes.
Credits to this answer goes to https://github.com/valvoline/SATextField/blob/master/SATextField/ContentView.swift. This answer wouldn't be here without the above source.
Nothing major changed.
struct CustomTextField: View {
#State var text: String
#State var pos: CGPoint
var body: some View {
StrokeTextLabel(text: $text, changeHandler: { newString in
self.text = newString
}, onCommitHandler: {
print("commitHandler")
})
.position(pos)
.gesture(dragGesture)
}
var dragGesture : some Gesture {
DragGesture()
.onChanged { value in
self.pos = value.location
print(self.pos)
}
}
}
Added UITextField and UITextFieldDelegate to use their functions
class WrappableTextField: UITextField, UITextFieldDelegate {
var textFieldChangedHandler: ((String)->Void)?
var onCommitHandler: (()->Void)?
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
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?()
}
}
Use the delegate as an object and add the attributes. Also made the string a Binding
struct StrokeTextLabel: UIViewRepresentable {
private let tmpView = WrappableTextField()
#Binding var text: String
//var exposed to SwiftUI object init
var changeHandler:((String)->Void)?
var onCommitHandler:(()->Void)?
func makeUIView(context: Context) -> UITextField {
tmpView.delegate = tmpView
tmpView.onCommitHandler = onCommitHandler
tmpView.textFieldChangedHandler = changeHandler
let attributedStringParagraphStyle = NSMutableParagraphStyle()
attributedStringParagraphStyle.alignment = NSTextAlignment.center
let attributedString = NSAttributedString(
string: text,
attributes:[
NSAttributedString.Key.strokeColor : UIColor.white,
NSAttributedString.Key.foregroundColor : UIColor.black,
NSAttributedString.Key.strokeWidth : -4.0,
NSAttributedString.Key.font: UIFont(name: "impact", size: 50.0)!
]
)
tmpView.attributedText = attributedString
tmpView.autocorrectionType = .no
tmpView.keyboardType = .default
return tmpView
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
}
}