How to trigger action when textfield becomes active - swift

I have a CollectionViewController made up of custom cells. At this point my custom cells are only made up of UITextFields. When I click inside one of the textFields to begin typing I want to trigger an animation on the cell. For some reason I can't figure out why my animation isn't being triggered when I click inside the textField. When I conform to the UItextFieldDelegate protocol and try to trigger the action through the DidBeginEditing method it doesn't work. When I try to trigger the animation through UIControlEvents.touchDown it isn't working either.
#objc func animateCell(textField: UITextField) {
print("TextField active")
let cell = collectionView.cellForItem(at: indexPath)
UIView.animate(withDuration: 0.5, delay: 0, options: .allowAnimatedContent, animations: ({
cell?.frame = collectionView.bounds
collectionView.isScrollEnabled = false
}), completion: nil)
}

Instead of textFieldDidBeginEditing, try using textFieldShouldBeginEditing: (and make sure to return true from that method since you want to allow the user to type :-).
Also, since you say the method doesn't fire until you click on a different textfield outside of the first one selected, fire the animation manually for the initially selected text field as the collection view is displayed.

Related

tableViewSelectionDidChange with addSubview and removeFromSuperview in table row

I looked for posts for issues with all 3 functions listed in the title, but my problem is not discussed anywhere. Frankly, my problem is such that it defies logic IMHO.
On selection of a row in my view-based NSTableView, I want to show a "Save" button in my last (aka "Action") column. So on viewDidLoad() I created my orphan button. Then on tableViewSelectionDidChange() I remove it from the superview and add it to my column's NSTableCellView. When there's no row selected, there is no button to show. Simple. Below code works perfectly.
func tableViewSelectionDidChange(_ notification: Notification) {
let selectedRow = tableView.selectedRow
btnSave!.removeFromSuperview()
if selectedRow > -1,
let rowView = rowView(selectedRow),
let actionView = rowView.view(atColumn: Column.action.hashValue) as? NSTableCellView {
actionView.addSubview(btnSave!)
}
}
Wait. Did I say Perfect? It works as long as I use mouse to change the selected row, including selecting below the last row in table which removes selection and any button in previous selected row. However, when I use the keyboard Up/Down keys to change the selection, it does not work.
First I thought the function will get called only if I use mouse to change row selection. However, that's not true. That's true for tableViewSelectionIsChanging as per docs and as per facts. But for tableViewSelectionDidChange docs don't say it will only work when mouse is used and the facts bear that out. I put print statements and function does get called. I stepped through debugger as well. The mind boggling part is - the method to remove button from superview works, but the one to add button as subview does not work - and only if I use keyboard. How is it possible that the same exact code executes but I get two different outcomes?
Adding remaining functions
I use this to get selected row
private func rowView(_ rowIndex: Int, _ make: Bool = false) -> NSTableRowView? {
return tableView.rowView(atRow: rowIndex, makeIfNecessary: make)
}
I call this in viewDidLoad to create my orphan button
private func createButton() {
btnSave = NSButton(frame: NSRect(x: 10, y: 0, width: 22, height: 16))
btnSave?.title = "S"
btnSave?.setButtonType(.momentaryPushIn)
btnSave?.bezelStyle = .roundRect
}

Deselect table cell when clicking away. Swift

I have the below table view but I want to be able to deselect the target cell when I click off to the side onto my view controller. I checked about using the deselect row at index path but not sure if theres a way to trigger that by clicking off to the side.
]
When you click off your table view, some event handler must be triggered in order for you to do a row deselection. So if the rest of your view controller is a static without user interaction, you can simply place a transparent UIView over the rest of your view controller and do deselect like this:
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.checkAction(sender:)))
self.YourTransparentView.addGestureRecognizer(gesture)
func checkAction(sender : UITapGestureRecognizer) {
if let indexPath = self.tableView.indexPathForSelectedRow{
self.tableView.deselectRow(at: indexPath, animated: true)
}
}

Swift: Can't get UITextField to dismiss keyboard

I have two textFields and a Done button in my VC but I'm having some problems with the ending of editing.
My textFieldDidEndEditing method is called when I tap on one textField after being inside the other one, or when I tap outside the textField (because I added a tap recognizer to the parent view) but not when I tap the Done button.
And, most important (especially when I run on an actual device), the keyboard won't disappear under any of these circumstances, even though my textFieldDidEndEditing method calls resignFirstResponder().
Why isn't the keyboard dismissing? Also, is there a way to have textFieldDidEndEditing get called when I tap outside the field just automatically (without having it come from the tap recognizer)? It just seems like this should be how it works, but if I'm wrong, I'm wrong.
Here's some pertinent parts of my code.
1.Trying to dismiss the keyboard. The first part of this method works, and the value is stored (when the method is called at all, that is). At no point does the cursor disappear from the textField, nor does the keyboard get dismissed.
func textFieldDidEndEditing(_ textField: UITextField) {
if let playerName = textField.text, let playerNum = nameFields.index(of: textField) {
playerNames[playerNum] = playerName
}
resignFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textFieldDidEndEditing(textField)
return true
}
Also, here's a curious thing: when I set a breakpoint in textFieldDidEndEditing and debug, enter a value in one field and hit Done, it segues to the next scene, and then stops at textFieldDidEndEditing, which at this point has no effect (the values may be stored but they aren't reflected in the new scene).
2.Trying to add the tap recognizer to the done button. I don't have an outlet to the done button in my code, just out of laziness, so that's probably the best solution. But, I'm still interested in why this doesn't work. This is identical to the code that defines the tap recognizer that's working in the parent view.
func dismiss(_ sender:UITapGestureRecognizer) {
nameFields.forEach { textFieldDidEndEditing($0) }
}
override func viewDidAppear(_ animated: Bool) {
for view in view.subviews where view is UIButton {
let dismissTextField = UITapGestureRecognizer(target: self, action: #selector(dismiss(_:)))
dismissTextField.numberOfTapsRequired = 1
view.addGestureRecognizer(dismissTextField)
}
}
You need to call resignFirstResponder inside textFieldShouldReturn method instead of calling textFieldDidEndEditing.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
Also in your TapGesture method simply call endEditing(_:) with your view instead of looping through the array of textFields.
func dismiss(_ sender:UITapGestureRecognizer) {
self.view.endEditing(true)
}
Swift 5: This solution below is much easier actually.
Simply subclass your ViewController with the text fields to UITextFieldDelegate like this:
class CreateGroupViewController: UIViewController, UITextFieldDelegate {
then if you named your UITextFields: nameTextField & occupationTextField then add the following delegations to your viewDidLoad() method:
self.nameTextField.delegate = self
self.occupationTextField.delegate = self
Then simply add the generic function to your file:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
If you do it this way, then for every textField reference outlet you create, all of them will hide the keyboard after the user hits the return button no matter which textField he's typing. For each textField you add to your view, add another self.nextTextField.delegate = self line to your viewDidLoad.
Remember to test this with your iPhone/iDevice plugged into your developer computer bc XCode's simulator doesn't bring up the keyboard (bc you're normally testing using a full keyboard). Or if you've set up your testing hardware (iPhone) via WiFi you can do it that way also. Otherwise, your users will be "testing" this on TestFlight.

Add custom recognizer delay

I've disabled delaysContentTouches in my tableview subclass using:
delaysContentTouches = false
subviews.forEach { ($0 as? UIScrollView)?.delaysContentTouches = false }
But in one of my sections, I still want to keep the delay. Is there a way to cancel the delay for certain sections or perhaps I can add a custom recognizer delay to a section?
Sections are not actual objects within a tableView, so my answer to your first question is no. The .delaysContentTouches applies to the entire tableView.
For your second inquiry, I believe that one way it could be possible is through setting a delay for desired cells' scrollView subview. In your tableView(cellForRowAt: indexPath) func, you could have something like this:
if indexPath.section == 3 { //or whatever your desired section is
for view in cell.subviews {
if view is UIScrollView {
let currentView = view as! UIScrollView
currentView.delaysContentTouches = true
}
}
}
This will find the UIScrollView in your cell's subviews in your desired section. It will then set the .delaysContentTouches property accordingly.
I have not personally executed this code, just researched it, so let me know if it works.
Edit
Apparently the UIScrollView in UITableViewCell has been deprecated, so the above method will not work anymore.
My next best suggestion to you is to use a UILongPressGuestureRecognizer. This will not be quite the same thing as delaying the touch, but could have a similar effect in real execution.
You could use it in the same tableView(cellForRowAt: indexPath) func as so:
let press = UILongPressGestureRecognizer(target: self, action: #selector(handlePress))
press.minimumPressDuration = 2.0 //however long you want
cell.addGestureRecognizer(press)
Whatever you are trying to achieve by selecting certain rows of your tableView could be placed in the handlePress func above which would be trigged upon the long press.

Cross-Disolve transition just changes instantly - Swift 3.0

I'm trying to change the color of a button's background using the cross dissolve animation, however, whenever I run this it instantly change the color instead of having a smooth transition.
UIView.transition(with: self.SignIn,
duration:3,
options: UIViewAnimationOptions.transitionCrossDissolve,
animations: { self.SignIn.backgroundColor? = UIColor(hexaString: "#" + hex) },
completion: nil)
Instead of setting backgroundColor in animations block I have set it before the view transition and then start the transition of view and it will work perfectly.
self.btnAnimate.backgroundColor = UIColor.red
UIView.transition(with: self.btnAnimate,
duration: 3,
options: .transitionCrossDissolve,
animations: nil,
completion: nil)
Output
Note: I have set red color as backgroundColor you can set what ever color you want.
This is happening because your animations are conflicting with the default button animations.
To solve this, you can create a custom child class of UIButton:
import UIKit
class CustomButton: UIButton {
func animateButton(hex: String) {
// This block disables the default animations
UIButton.performWithoutAnimation {
// This is where you define your custom animation
UIView.transition(with: self, duration:3, options: UIViewAnimationOptions.transitionCrossDissolve, animations: {
self.backgroundColor? = UIColor(hexaString: "#" + hex) }, completion: nil)
// You must call layoutIfNeeded() at the end of the block to prevent layout problems
self.layoutIfNeeded()
}
}
}
Then to call this function:
signInButton.animateButton("ffffff") // Pass in your hex string
Of course, be sure to change the class of your loginButton on the story board from UIButton to CustomButton.
The downside to this approach is that the text is still darkened during the animation process. There may be a way to override this (I wasn't able to quickly determine how), so an alternative approach to this solution is to use a UILabel instead of a UIButton and set an on click listener.
Or another approach you might want to consider, is put a customLabel behind a UIButton and perform animations on the label when the button is clicked. Although this approach seems somewhat "hacky", the advantage is that the button is not disabled while the label is being animated, meaning you can register rapid sequential button presses (although it looks like the purpose of your button is to log the user in, in which case this probably wouldn't be necessary for you).