How to change UIBarButtonItem text during runtime "cleanly" (Edit --> Done --> Edit)? - ios

I'm trying to change UIBarButtonItem text "cleanly" during runtime so that Edit/Done modes can be toggled. Every time I change the title attribute during runtime, however, the animation seems clumsy. I'm looking to emulate the appearance and function of the Edit/Done button in the Contacts app (where Edit simply fades and Done appears in its place).
#IBAction func editDoneButtonPressed(sender: UIBarButtonItem) {
if(sender.title == "Edit"){
sender.title = "Done"
}else{
sender.title = "Edit"
}
}
Initial settings: Identifier is set to "custom" and Title is set to "Edit"
Simpler programatical solutions are preferred, however, the appearance of the animation is indeed paramount. I'd consider toggling the UIBarButtonItem identifier, rather than its text attribute, however, I'm not sure of an elegant way to toggle the identifier during runtime.
BTW: I'm using Swift to construct the app.
Links to...
Screencast of Contacts app Edit/Done toggle animation:
https://youtu.be/5mT_vzpNhIw
Screencast of my Edit/Done toggle animation:
https://youtu.be/mEweotJNVNE
Thank you.

There's a much better way, a standard way, to get an Edit/Done button.
UIViewController provides a standard Edit/Done button that is already hooked into the editing property of the view controller.
A common usage is as follows:
Inside the viewDidLoad method of your view controller, use the standard button:
self.navigationItem.rightBarButtonItem = editButtonItem
Put the editButtonItem wherever you actually need it.
Then, instead of setting up your own action, simply override the setEditing(_:animated:) method:
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if (editing) {
// user just tapped the Edit button (it now says Done)
} else {
// user just tapped the Done button (it now says Edit)
}
}

Related

UISearchController Needs an extra tap to become first responder

I switched my apps one screen from UISearchBar to UISearchController. It's a tableview controller. As per design I should not keep the search bar on UI initially unless it is activated, (Normally it's a common practice to keep search bar as the 'tableHeaderView'). The problem was, I have a search button, when tapped 'search bar' should be activated and become first responder.
When tapped on cancel button, it should be removed from UI. However when I'm tapping on the 'Search Bar Button' on navigation bar, the UISearchController gets activated, providing a dim background but the keyboard doesn't appear. I need to tap one more time on search bar to bring the keyboard upon UI.
Here's my search bar button action:
#IBAction func onTapSearch(_ sender: AnyObject) {
self.view.addSubview(searchController.searchBar)
searchController.isActive = true
searchController.becomeFirstResponder()
isSearchActive = true
self.navigationController?.setToolbarHidden(true, animated: false)
}
I'm configuring the UISearchController in my viewDidLoad method. Let me know if that part code is any of you want to see, however it's usual code. And I verified I'm not calling anywhere resignFirstResponder() method anywhere.
try this,
Just replace this line,
searchController.becomeFirstResponder()
With this below,
searchController.searchBar.becomeFirstResponder()
Edit,
func didPresentSearchController(_ searchController1: UISearchController) {
searchController1.searchBar.becomeFirstResponder()
}
Implement this delegate method and try.

What is triggered when "Done" is pressed after changing from "Edit" in UINavigationItem bar for UITableViewController?

Short: In Swift/iOS, when does the "Done" (which was previously "Edit") nav bar button trigger when getting out of the UITableViewController "Edit" mode? When the user presses "Done" I'd like to enable a "+" button in my UINavigationItem bar so the user can once-again add rows by migrating to another view controller.
Longer: When a UITableViewController is showing below a UINavigationItem nav bar, there is an "Edit" button which turns into "Done" after it's clicked to enable deletes & move/drags. Works great when this button is enabled via uncommenting code in viewDidLoad() generated as part of the UITableViewController class:
self.navigationItem.leftBarButtonItem = self.editButtonItem
I've got my move/drags & deletes working fine, but I want to appropriately disable my "+" button (addBarButton, used for navigating to another view controller to add a new row) while the user is in the Edit mode. I'd then like to re-enable addBarButton after the user has clicked "Done" (which turns back into "Edit").
It looks like disabling addBarButton during the func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) is correct. If I read Apple's docs right, this is triggered when user presses Edit in the nav bar. What I don't know is what is triggered when the user presses "Done" (the button formerly labeled "Edit"). If I enable my addBarButton "+" button after the func tableView with moveRowAt, this enables addBarButton before the user has pressed "Done".
The Apple doc I'm referencing is at:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/TableView_iPhone/ManageReorderRow/ManageReorderRow.html#//apple_ref/doc/uid/TP40007451-CH11-SW1
Apologies if I'm missing something obvious. Thx
The answer is right in the description for the UIViewController editButtonItem documentation:
If one of the custom views of the navigationItem property is set to the returned object, the associated navigation bar displays an Edit button if isEditing is false and a Done button if isEditing is true. The default button action invokes the setEditing(_:animated:) method.
The last sentence is the key. You should override the setEditing(_:animated:) method in your table view controller subclass. Be sure you call the super implementation and then perform whatever custom action you want based on whether the controller is entering or exiting editing mode.
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if (editing) {
// User tapped the Edit button, do what you need
} else {
// User tapped the Done button, do what you need
}
}

Overwrite ios back button

I have an app that displays a webView of a web page. The web consists of a main page with a lot of subpages. When I am on the main page I want the navigation Back button to to keep the default behaviour of going back to the previous view controller. But when the web is on a subpage I would like the navigation Back button to take the web to the main web page.
So I need to catch when the user presses the back button and if he is on a web subpage take him to the web main page and prevent the webView from closing. I was thinking that I can catch the back button using viewWillDisappear(), but how can I prevent the view from closing?
Similar to KickimusButticus' answer:
If you want to more easily do it using the storyboard and such, you could hook up your back button to an IBAction like so:
#IBAction func back() {
}
And have internal testing for whether or not the user is in a sub-page of the webview. If they are, you can mess with the webview. If they are on the home page, you could simply use the back button to segue to your main view controller or use the self.dismissViewControllerAnimated(true, completion: nil) to dismiss that view controller.
Check this post out:
Execute action when back bar button of UINavigationController is pressed
Something like this ought to do, in your view controller code:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let customBackButtonItem = UIBarButtonItem(title: "Back", style: .Bordered, target: self, action:#selector(customBackMethod(_:)))
self.navigationItem.leftBarButtonItem = customBackButtonItem
}
// #objc is so we can use #selector() up above
#objc func customBackMethod(sender: UIBarButtonItem) {
if webView.canGoBack {
webView.goBack()
}
}
Note that you may have to use UIBarButtonItem(image:, style:, target:, action:) if you want a 'back' image, or UIBarButtonItem(customView:). See this question and answer for more details: https://stackoverflow.com/a/36180934/892990

Touch Up Inside not working properly

I have an app with some buttons, when those buttons are pressed the image on them should change. I assume that the TouchUpInside runs when you tap and remove the finger while still holding inside the area of the element, however it only works rarely and I'm not sure why.
The reason I use TouchUpInside instead of TouchDown is because I want the user to be able to cancel the action.
I'm sorry if I've misunderstood anything about those events and if this has already been asked. I couldn't find an answer to my problem searching the web.
//The IBAction is set to trigger on TouchUpInside
#IBAction func action11(sender: UIButton) {
setTile(sender)
}
func setTile(sender: UIButton) {
if turn {
print("O's turn")
sender.setImage(xTile, forState: .Normal)
turn = false
}
}
EDIT: Added the necessary code
There are some properties of UIButtons which you can use to achieve what you want.
You can use Default and selected state of uibutton to set two different images.
In XIB select state "Default" and assign default image to that state again select state to "Selected" and assign image which you want after button section.
and add following line in button selection method.
-(IBAction)buttonTapped:(UIButton *)sender{
sender.selected = !sender.selected;
}
Your understanding is correct, you need to use touchUpInside.
I assume you are trying to create a button that has a toggle function. On one touch you want the button to have the value Say "X" and when touched again the button has a value "O".
Take a look at this code below, this should do the job.
class ViewController: UIViewController {
var isButtonPressed = false{
// Adding a Property Observer, that reacts to changes in button state
didSet{
if isButtonPressed{
// Set the Value to X.
}else{
// Set the Value to O.
}
}
}
#IBAction func changeButtonValue(sender: UIButton) {
// Toggle the button value.
isButtonPressed = !isButtonPressed
}
}
If you don't set turn=true after the first time, this code is executed it will be executed only one.
if turn {
print("O's turn")
sender.setImage(xTile, forState: .Normal)
turn = false
}
Check if the button frame is large enough to get finger touch.
Apple says at least 35x35 pixel.

What's the simplest way to receive tap events on a disabled UIButton?

I have a UIButton on a form, and want to put it in a disabled state when the form is incomplete. However, I still want to be able to detect if a user attempts to press the button even in its disabled state so that the interface can let the user know that certain required fields on the form are not filled-in yet (and perhaps scroll to that field and point it out, etc.).
There doesn't seem to be any straightforward way to do this. I tried simply attaching a UITapGestureRecognizer to the UIButton but it doesn't respond when the button is in a disabled state.
I'd like to avoid subclassing UIButton if possible, unless it's the only way.
Create a fallback button. Put it behind the main button. Set its background and text colors to [UIColor clearColor] to ensure it won't show up. (You can't just set its alpha to 0 because that makes it ignore touches.) In Interface Builder, the fallback button should be above the main button in the list of subviews, like this:
Give it the same frame as the main button. If you're using autolayout, select both the main and fallback buttons and create constraints to keep all four edges equal.
When the main button is disabled, touches will pass through to the fallback button. When the main button is enabled, it will catch all the touches and the fallback button won't receive any.
Connect the fallback button to an action so you can detect when it's tapped.
Based on #rob idea, I sub-class a UIButton, and add a transparent button before someone addSubview on this button.
This custom UIButton will save many time about adjusting the UI components on the storyboard.
Update 2018/08
It works well, and add some enhanced detail to this sub-class. I have used it for 2 years.
class TTButton : UIButton {
// MARK: -
private lazy var fakeButton : UIButton! = self.initFakeButton()
private func initFakeButton() -> UIButton {
let btn = UIButton(frame: self.frame)
btn.backgroundColor = UIColor.clearColor()
btn.addTarget(self, action: #selector(self.handleDisabledTouchEvent), forControlEvents: UIControlEvents.TouchUpInside)
return btn
}
// Respect this property for `fakeButton` and `self` buttons
override var isUserInteractionEnabled: Bool {
didSet {
self.fakeButton.isUserInteractionEnabled = isUserInteractionEnabled
}
}
override func layoutSubviews() {
super.layoutSubviews()
// NOTE: `fakeButton` and `self` has the same `superView`.
self.fakeButton.frame = self.frame
}
override func willMoveToSuperview(newSuperview: UIView?) {
//1. newSuperView add `fakeButton` first.
if (newSuperview != nil) {
newSuperview!.addSubview(self.fakeButton)
} else {
self.fakeButton.removeFromSuperview()
}
//2. Then, newSuperView add `self` second.
super.willMoveToSuperview(newSuperview)
}
#objc private func handleDisabledTouchEvent() {
//NSLog("handle disabled touch event. Enabled: \(self.enabled)")
self.sendActionsForControlEvents(.TouchUpInside)
}
}
You have a great misunderstanding of user experience.
If a button is disabled, it is meant to be non-interactable.
You can not click on a disabled button, that is why it is disabled.
If you want to warn users about something when that button is clicked (e.g. form not filled correctly or completely), you need to make that button enabled. And just warn users when they click on it, instead of proceeding further with app logic.
Or you can keep that button disabled until form criteria are met, but show what is wrong with the form using another way, like putting exclamation marks near text fields, changing text field colors to red, or something like that...
But never try to add gesture recognizers, or hidden fallback buttons to a disabled button.
Check those and let me know if you see a disabled button:
https://airbnb.com/signup_login
https://spotify.com/us/signup/
https://netflix.com/signup/regform
https://reddit.com/register/
https://twitter.com/signup
https://facebook.com/r.php
https://appleid.apple.com/account
https://accounts.google.com/SignUp
https://login.yahoo.com/account/create
https://signup.live.com/signup
All the proceed buttons on these websites are always enabled, and you get feedback about what is wrong when you try to continue.
And here is really good answer: https://ux.stackexchange.com/a/76306
Long story short: disabled UI elements meant to be not-interactable.
Trying to make them interactable while they are disabled is the same to making them enabled in the first place.
So, for your question's case, it is just a styling issue. Just try styling your button, instead of making it disabled/enabled.

Resources