The test field was originally centered. However, as I tap on it, it was brought to the left.
This UI issue occurs on macOS 13.2, built on My Mac(Designed for iPad), while running on iPad is totally fine.
Xcode 14.2 and 14.1 has the same behaviour.
import UIKit
class ViewController: UIViewController {
let textField: UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.backgroundColor = .green
textField.textAlignment = .center
textField.text = "Hello, World"
return textField
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textField)
NSLayoutConstraint.activate([
textField.widthAnchor.constraint(equalToConstant: 200),
textField.heightAnchor.constraint(equalToConstant: 50),
textField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
textField.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
}
How can I fix this?
Related
I have one textField.
private var verseTitle: UITextField = {
let tf = UITextField()
tf.placeholder = "TITLE"
tf.font = UIFont(suite16: .tBlackItalic, size: 18)
tf.textColor = .black.withAlphaComponent(0.5)
tf.returnKeyType = .done
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
In viewDidLoad method, I have assigned self as delegate.
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
verseTitle.delegate = self
}
In viewDidLayout method, I'm using stack view to add textField to the view.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//Title
let titleStack = UIStackView()
titleStack.axis = .horizontal
titleStack.alignment = .center
titleStack.distribution = .equalSpacing
titleStack.spacing = 8
titleStack.addArrangedSubview(verseTitle)
titleStack.addArrangedSubview(floorView)
titleStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleStack)
NSLayoutConstraint.activate([
floorView.heightAnchor.constraint(equalToConstant: 13),
floorView.widthAnchor.constraint(equalToConstant: 13),
titleStack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
titleStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
])
}
Now, the problem I'm facing is, that when I start typing in the textField, the keyboard gets dismissed only after I type one letter. I'm not sure why this is happening. I have to tap on the field after entering each letter. For some reason, the focus is taken away from the field after each letter is entered (unless I tap on a suggested autocorrect - the whole string is correctly added to the string at once)
What's going on here is your view, including the UITextField gets re-created after each keystroke because your view construction is in: viewDidLayoutSubviews(), When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method.
To fix it, move the code to ViewDidLoad, so the view is created only once:
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
verseTitle.delegate = self
let titleStack = UIStackView()
titleStack.axis = .horizontal
titleStack.alignment = .center
titleStack.distribution = .equalSpacing
titleStack.spacing = 8
titleStack.addArrangedSubview(verseTitle)
titleStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleStack)
NSLayoutConstraint.activate([
titleStack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
titleStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
])
}
I've picked up on a weird little bug that I can't fix. When moving between textfields, the toolbar (associated as the textfield's input accessory view) and keyboard are flickering. It's only happening on a physical device, specifically when the textfield's textContentType = .password and when using code to move between textfields, e.g. in this case, the "Jump" toobar button which flips between textFields. I'm attaching a couple of screenshots that show what the glitch shows. It basically collapses the auto-complete Passwords toolbar for a micro second and then shows it again:
I've tested other code to move the cursor to the password textfield and it too shows the same glitch. I think it's something to do with calling .becomeFirstResponder but I can't work out how to fix it. It's happening not just with the .password option of .textContentType but also others, e.g. .username, .familyName. I need to fix it because, not only does it cause that slight screen glitch, but in my full project, I have a bunch of other stuff on the screen that moves based on the location of the keyboard and this glitch triggers the keyboard observer which in turn update the keyboard frame.
I've created a blank project and the same issue happens. Here is the code. If anyone can help, it would be greatly appreciated.
class ViewController: UIViewController {
var keyboardToolbar: UIToolbar!
var emailTextField : UITextField!
var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
keyboardToolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let jumpButton = UIBarButtonItem(title: "Jump", style: .plain, target: self, action: #selector(jumpToolBarButtonTapped))
keyboardToolbar.items = [jumpButton]
keyboardToolbar.sizeToFit()
emailTextField = UITextField()
emailTextField.placeholder = "Email Address TextField"
emailTextField.keyboardType = .emailAddress
emailTextField.textContentType = .emailAddress
emailTextField.autocapitalizationType = .none
emailTextField.inputAccessoryView = keyboardToolbar
view.addSubview(emailTextField)
emailTextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
emailTextField.topAnchor.constraint(equalTo: view.topAnchor, constant: 100),
emailTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
])
passwordTextField = UITextField()
passwordTextField.placeholder = "Password TextField"
passwordTextField.keyboardType = .default
passwordTextField.autocapitalizationType = .none
passwordTextField.textContentType = .password
passwordTextField.inputAccessoryView = keyboardToolbar
view.addSubview(passwordTextField)
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
passwordTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 20),
passwordTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
passwordTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
])
}
#objc func jumpToolBarButtonTapped() {
if emailTextField.isFirstResponder == true{
DispatchQueue.main.async { [self] in
passwordTextField.becomeFirstResponder()
}
} else {
DispatchQueue.main.async { [self] in
emailTextField.becomeFirstResponder()
}
}
}
}
I've been having this issue with HoshiTextField for quite some time now, I also opened an issue on git but didn't get an answer...
This is how it should look like and how it looks like if the user is selecting the textField:
The problem occurs when setting the textField to becomeFirstResponder inside viewDidLoad or when popping a ViewController while the textField inside the first VC was selected. Apparently that messes up the frames or constraints of the textField but I have absolutely no idea how to fix this.
As you can see the "Email-Adesse"-text is moving to the upper left and when pushing and poping back to the ViewController it moves even further outside the constraints. When checking the View Hirarchy with the debugger the "Email-Adresse"-Text looks perfectly in place even though it isn't. Setting up the constraints inside viewDidAppear didn't change anything.
I constrain the textFields like every other element:
let emailTextField: HoshiTextField = {
let v = HoshiTextField()
v.borderActiveColor = .white
v.borderInactiveColor = .white
v.textColor = .white
v.font = UIFont(name: "AvenirNext-Regular", size: 17)
v.placeholder = "Email-Adresse"
v.placeholderColor = .white
v.placeholderFontScale = 0.8
v.minimumFontSize = 13
v.borderStyle = .line
v.autocapitalizationType = .none
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
emailTextField.topAnchor.constraint(equalTo: theLabel.bottomAnchor, constant: 20).isActive = true
emailTextField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 30).isActive = true
emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
emailTextField.heightAnchor.constraint(equalToConstant: 60).isActive = true
If anyone can help me out here I would be so grateful! I hope the problem is clear, you can also look at my project to see the problem yourself:
Git repo to my project
Here is full test controller with 2 alternates of possible solution
Demo: Alternate 1 - often appears already expanded
Demo: Alternate 2 - always there is a delay, expanding is visible
class ViewController2: UIViewController {
#IBOutlet weak var theLabel: UILabel!
private weak var emailTextField: HoshiTextField!
override func viewDidLoad() {
super.viewDidLoad()
emailTextField = {
let v = HoshiTextField()
v.borderActiveColor = .white
v.borderInactiveColor = .white
v.textColor = .white
v.font = UIFont(name: "AvenirNext-Regular", size: 17)
v.placeholder = "Email-Adresse"
v.placeholderColor = .white
v.placeholderFontScale = 0.8
v.minimumFontSize = 13
v.borderStyle = .line
v.autocapitalizationType = .none
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
self.view.addSubview(emailTextField)
emailTextField.topAnchor.constraint(equalTo: theLabel.bottomAnchor, constant: 20).isActive = true
emailTextField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 30).isActive = true
emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
emailTextField.heightAnchor.constraint(equalToConstant: 60).isActive = true
// DispatchQueue.main.async {
// self.emailTextField.becomeFirstResponder() // Alternate 1
// }
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
emailTextField.becomeFirstResponder() // Alternate 2
}
}
Sorry I misunderstood your question at first.
I feel like the issue is that at some point as the HoshiTextField is selected and leaves the screen, it becomes deselected, but it does not update it's layout. So then when you come back the animations still stay relatively the same, but from a different angle.
I'm not sure how to solve that problem precisely, but I was able to get the screen to look normal after adding the following to your EmailVC
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
view.endEditing(true)
}
I hope that that's fine. It's not really solving the issue, but it does get the UI of your app looking proper in this scenario.
Something as simple as the following code wont work for me to autofill an email for example, I've tried with phonenumbers, given names etc. as well. Even after forcing autocorrectionType.
let textField = UITextField()
textField.textContentType = .emailAddress
textField.autocorrectionType = .yes
textField.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textField)
NSLayoutConstraint.activate([
textField.centerYAnchor.constraint(equalTo: view.centerYAnchor),
textField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
textField.widthAnchor.constraint(equalToConstant: 100),
textField.heightAnchor.constraint(equalToConstant: 50)
])
Swift 5, Xcode 10.2, iOS 12.2
Try it this way. After typing UITextContentType. , You will get suggestions.
let test = UITextField()
test.textContentType = UITextContentType.emailAddress
I am using the good old add a zero length character solution to detect backspaces in an empty text field (Detect backspace Event in UITextField).
Unfortunately on an iPad if you connect an external keyboard and hit "cmd+backspace" it does not trigger the shouldChangeCharactersInRange method.
I looked around the documentation and the decompiled headers and I don't seem to be able to find a way to prevent that.
So, how do I detect a "command + backspace event"?
You can use a UIKeyCommand. I tested on my 10.5" iPad Pro running iOS 12.1.1, using a Bluetooth keyboard and the Swift Playgrounds app version 2.2.
Here's the playground code:
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
override func loadView() {
let command = UIKeyCommand(input: "\u{8}", modifierFlags: .command, action: #selector(ViewController.handleKeyCommand), discoverabilityTitle: "Hello")
addKeyCommand(command)
let view = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
view.contentMode = .topLeft
view.backgroundColor = .white
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 8
stack.alignment = .fill
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: stack.leadingAnchor),
view.trailingAnchor.constraint(equalTo: stack.trailingAnchor),
view.topAnchor.constraint(equalTo: stack.topAnchor)])
let textField = UITextField(frame: CGRect(x: 20, y: 20, width: 260, height: 30))
textField.borderStyle = .roundedRect
textField.translatesAutoresizingMaskIntoConstraints = false
stack.addArrangedSubview(textField)
label.translatesAutoresizingMaskIntoConstraints = false
stack.addArrangedSubview(label)
self.view = view
}
#objc func handleKeyCommand(_ sender: UIKeyCommand) {
commandCount += 1
label.text = "\(commandCount)"
}
private var commandCount = 0
private let label = UILabel()
}
let vc = ViewController()
PlaygroundPage.current.liveView = vc
Tap in the text field, then press ⌘⌫. Each time you press it, the count in the label increases by 1.