How to change the function of a UIBarButtonItem on event in Swift - swift

I've been following this Youtube tutorial from Let's Build That App to achieve a step by step navigation system using the Google Maps SDK. I want to be able to change the programmatically-generated 'next' UIBarButtonItem to a 'done' button on the last map location and use this to go back to a previous ViewController.
What's the correct method of going about this using Swift?
https://www.youtube.com/watch?v=8wPjCdDn2wo
My code:
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "Next",
style: .plain,
target: self,
action: #selector(nextLocation)
)
}
func nextLocation() {
if currentDestination == nil {
currentDestination = destinations.first
}
else {
if let index = destinations.index(of: currentDestination!), index < destinations.count - 1{
currentDestination = destinations[index + 1]
}
}
setMapCamera()
}
private func setMapCamera() {
CATransaction.begin()
CATransaction.setValue(2, forKey: kCATransactionAnimationDuration)
mapView?.animate(to: GMSCameraPosition.camera(withTarget: currentDestination!.location, zoom: currentDestination!.zoom))
CATransaction.commit()
let marker = GMSMarker(position: currentDestination!.location)
marker.title = currentDestination?.name
marker.map = mapView
}
/* My incorrect code */
func lastLocation() {
if currentDestination == destinations.last {
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "Finish",
style: .plain,
target: "myItinerary",
action: #selector(lastLocation)
)
}
}

I Hope this will help you :
First : Detect Last location or bar button click after last location to change bar button label to "Done". Then set action to the button after button "Done" clicked change again to "Next" and set destination counter to first value.
func next() {
if currentDestination == nil {
currentDestination = destinations.first
} else {
if let index = destinations.index(of: currentDestination!), index < destinations.count - 1 {
currentDestination = destinations[index + 1]
}
else {
// After last Location change button title to "Done"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(lastLocation))
}
}
setMapCamera()
}
func lastLocation() {
currentDestination = destinations.first // Set destinations counter to First value
// Change Title of Bar Button
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(ViewController.next as (ViewController) -> () -> ()))
}
If you want to go backward location (In reverse) then :
func lastLocation() {
currentDestination = destinations.last
destinations = destinations.reversed()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(ViewController.next as (ViewController) -> () -> ()))
}

Related

UIDatePicker: select current date (swift)

I have a UIDatePicker in which some date is set by initial. With addTarget, I listen to thevalueChanged events and everything works correctly when the wheel is spinning.
I want to allow users to directly select the initial date (for example, by clicking on it). But now the event comes only when the wheel is spinning. To put the current date, the user will have to:
Put a new date
Go to the previous date
My code:
class Picker {
init() {
let datePicker = UIDatePicker()
datePicker.setDate(Date(), animated: false) // Set initial date
datePicker.addTarget(self, action: #selector(handleDatePicker(sender:)), for: .valueChanged)
}
#objc func handleDatePicker(sender: UIDatePicker) {
print(sender.date)
}
}
Picker()
Screenshot:
SOLUTION:
The most correct solution turned out to be the addition the UIToolBar with Done button. The code can be found here: example with UIToolBar
to select first index value on click done button in datePicker
func pickUp(_ textField : UITextField){
// UIPickerView
self.myPickerView = UIPickerView(frame:CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 216))
self.myPickerView.delegate = self
self.myPickerView.dataSource = self
self.myPickerView.backgroundColor = UIColor.white
textField.inputView = self.myPickerView
// ToolBar
let toolBar = UIToolbar()
toolBar.barStyle = .default
toolBar.isTranslucent = true
toolBar.tintColor = UIColor.darkGray
toolBar.sizeToFit()
// Adding Button ToolBar
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(UrVC.doneClick))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(UrVC.cancelClick))
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
textField.inputAccessoryView = toolBar
}
#objc func doneClick() {
pickerTxtfld.resignFirstResponder()
let indexPath = myPickerView.selectedRow(inComponent: 0)
pickerTxtfld.text = dataArray[indexPath]
}
#objc func cancelClick() {
pickerTxtfld.resignFirstResponder()
}

App crashed when pickerView bar button pressed in iOS 12 but not in iOS 11

I am using custom textfield to show datepicker and pickerview. I have added UIToolBar as inputAccessoryView. I have added done and cancel barbutton. when i press done and cancel button action is working fine in iOS 11. But In iOS 12 app is crashing on tap of bar button. Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_UIButtonBarButton _setContentCoverViewMode:]: unrecognized selector sent to instance.
#IBDesignable
final class ICTextField: UITextField {
private var pickerView: UIPickerView?
/// To show the date or time input from textfield set this value to true
/// Default is false.
#IBInspectable open var isDatePicker: Bool = false {
didSet {
if self.isDatePicker {
self.inputView = self.datePicker
self.inputAccessoryView = self.configureToolBar()
}
self.reloadInputViews()
}
}
/// Shows the inputview as UITableView. able to select multiple option, default is false
#IBInspectable open var isDropDown: Bool = false {
didSet {
if isDropDown {
self.pickerView = UIPickerView()
self.pickerView?.dataSource = self
self.pickerView?.delegate = self
self.inputAccessoryView = self.configureToolBar()
self.inputView = self.pickerView
self.reloadInputViews()
} else {
self.inputView = nil
self.reloadInputViews()
}
}
}
// MARK: - Public Properties
let datePicker: UIDatePicker = {
let dateDropDown = UIDatePicker()
dateDropDown.datePickerMode = .date
dateDropDown.locale = Locale(identifier: "en_US_POSIX")
dateDropDown.addTarget(self, action: #selector(ICTextField.datePickerValueChanged(sender:)), for: .valueChanged)
return dateDropDown
}()
/// Tool bar to show cancel and done on top of textfield.
open var accessoryToolBar: UIToolbar? {
didSet {
self.inputAccessoryView = accessoryToolBar
self.reloadInputViews()
}
}
private func configureToolBar() -> UIToolbar {
let frame = CGRect(x: 0, y: 0, width: self.frame.width, height: 40.0)
let toolBar = UIToolbar(frame: frame)
toolBar.barStyle = .default
toolBar.barTintColor = UIColor(hex: "4db8ff")
let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(donePressed))
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(cancelPressed))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
toolBar.sizeToFit()
return toolBar
}
#objc fileprivate func donePressed() {
self.resignFirstResponder()
}
#objc private func cancelPressed() {
self.resignFirstResponder()
}
}
In viewcontroller I am using this textField Object.
icTextField.isDatePicker = true
Update
App is getting crashed when keyboard is open and it contains accessory view as toolbar. now if i press navigationbar barbutton or accessory toolbar button app is getting crashed. I think this is apple bug. anyone found solution.

How to add action to a programmatically-made button?

Since I was having issues creating a button via storyboard, I went about initiating a right nav bar button through code - found via this question How can I go back to the initial view controller in Swift?. This code is meant to take me back to my root view controller.
So here is the code as it stands at the moment.
let button1 = UIBarButtonItem(image: UIImage(named: "HomeM25.png"), style: .plain, target: self, action: #selector(getter: UIDynamicBehavior.action))
self.navigationItem.rightBarButtonItem = button1
func button() {
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
I was under the impression that if I were to change the
action: #selector(getter: action)
I would be able to create a function following this button initialization like so
func action() {
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
However, I am greeted with the "Use of local variable 'action' before its declaration'". I do not understand why this interpretation would not perform/why I would have to establish the action variable when its only use is a function name? Any help would be appreciated.
Update 1: Current Code
override func viewDidLoad() {
super.viewDidLoad()
let button1 = UIBarButtonItem(image: UIImage(named: "HomeM25.png"), style: .plain, target: self, action: #selector(action))
self.navigationItem.rightBarButtonItem = button1
func action() {
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
Update 1: Also had to use a different function action to go back to the original view controller.
self.navigationController!.popToRootViewController(animated: true)
It should be #selector(action). And you can define function within a function. Move action outside of the viewDidLoad function. Try this.
override func viewDidLoad() {
super.viewDidLoad()
let button1 = UIBarButtonItem(image: UIImage(named: "HomeM25.png"), style: .plain, target: self, action: #selector(action))
self.navigationItem.rightBarButtonItem = button1
}
func action() {
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
}

Swift 3 - Reload NavigationController Button Image

I have a navigation controller view, with an embedded collectionview controller. I have a right bar button added programmatically, with an image added to it as follows:
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "greyCircle").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector (handleGrayCircleButton))
I would like a user that clicks on this button, to be able to change the image from grey to silver. I have the button handler set up to switch from grey to silver with each press:
self.navigationItem.rightBarButtonItem?.image = UIImage(named: "greyCircle")
However, the image only changes briefly before switching back. I have tried to save the color of the image as a 'flag' variable, i.e. if it is currently grey, turn to silver, in order to make the change persist, however, the image continually resets to whatever it is loaded with (either with viewDidLoad or viewWillAppear).
Is there anyway to refresh/reload the navigationController tab bar after each click and make it persist, along with the images for the bar buttons?
#
EDIT: Following is full button handler.
func handleFavoritePress(){
print("Favorites pressed")
if(!returnTrueIfInFavorites(objectName: readString(object: self.patientRecord!, key: "name"), objectType: LIST_CoreDataBaseObjects.Patients)){
// Add to favorites
Favorites_Names.append(readString(object: self.patientRecord!, key: "name"))
Favorites_Types.append(LIST_CoreDataBaseObjects.Patients)
// Let user know
let patientName = readString(object: self.patientRecord!, key: "name")
let alertController = UIAlertController(title: "Alert!", message: "\(patientName) has been added to your favorites.", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
var rootViewController = UIApplication.shared.keyWindow?.rootViewController
if let navigationController = rootViewController as? UINavigationController {
rootViewController = navigationController.viewControllers.first
}
if let tabBarController = rootViewController as? UITabBarController {
rootViewController = tabBarController.selectedViewController
}
rootViewController?.present(alertController, animated: true, completion: nil)
self.navigationItem.rightBarButtonItem?.image = UIImage(named: "savedFavorite_1")
}else{
self.navigationItem.rightBarButtonItem?.image = UIImage(named: "favorite_2")
deleteFromFavorites(objectName: readString(object: patientRecord!, key: "name"), objectType: LIST_CoreDataBaseObjects.Patients)
}
for favorite in Favorites_Names{
print("Favorites are currently: \(patient)")
}
}
And other functions:
override func viewWillAppear(_ animated: Bool) {
collectionView?.backgroundView = setBackgroundImage(imageName: "whiteBackground")
navigationItem.title = "Patient"
if(returnTrueIfInFavorites(objectName: readString(object: patientRecord!, key: "name"), objectType: LIST_CoreDataBaseObjects.Patients)){
setupNavBarButtonsIsFavorited()
}else{
setupNavBarButtonsNotFavorited()
}
}
func setupNavBarButtonsNotFavorited(){
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "favorite_2").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector (handleFavoritePress))
}
func setupNavBarButtonsIsFavorited(){
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "savedFavorite_1").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector (handleFavoritePress))
}
The answer to your question lies within this two LIST_CoreDataBaseObjects.Patients, returnTrueIfInFavorites or the patientRecord variable.
Either LIST_CoreDataBaseObjects.Patients did not get updated on your function handleFavoritePress's code
/* this code */
Favorites_Names.append(readString(object: self.patientRecord!, key: "name"))
Favorites_Types.append(LIST_CoreDataBaseObjects.Patients)
/* this code */
Because from what you've explained in the comments section, if I understood it correctly, is that when you tap the button the image changes, and then you navigate to a different view, but then when you navigate back, viewDidAppear(_:) and viewWillAppear(_:) will be triggered.
Which in the code block below I will explain
// this function will be triggered everytime you navigate into this view (because it has appeared)
override func viewWillAppear(_ animated: Bool) {
/* don't forget to call super.viewWillAppear(true) */
super.viewWillAppear(true)
/* don't forget to call super.viewWillAppear(true) */
collectionView?.backgroundView = setBackgroundImage(imageName: "whiteBackground")
navigationItem.title = "Patient"
// the problem lies here
/* the function `(returnTrueIfInFavorites(objectName: readString(object: patientRecord!, key: "name"), objectType: LIST_CoreDataBaseObjects.Patients))` is always returning one thing which is why your image returns back to its previous state */
if (returnTrueIfInFavorites(objectName: readString(object: patientRecord!, key: "name"), objectType: LIST_CoreDataBaseObjects.Patients)) {
setupNavBarButtonsIsFavorited() // only one of these gets called
} else {
setupNavBarButtonsNotFavorited() // only one of these gets called
}
}
func setupNavBarButtonsNotFavorited() {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "favorite_2").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector (handleFavoritePress))
}
func setupNavBarButtonsIsFavorited() {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "savedFavorite_1").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector (handleFavoritePress))
}
All of these or you're just not updating the variable self.patientRecord!'s value (I haven't seen any setting in your code relating to this variable)

MWPhotoBrowser - how to implement delete photo functionality?

I am using MWPhotoBrowser for my app. I need to give delete functionality to my users. Is there any way we can implement delete a particular photo or multiple photos functionality?
Quick help needed.
I did this in swift by adding this extension from outside of the library:
extension MWPhotoBrowser {
public override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let _ = valueForKey("_gridController") as? MWGridViewController {
let leftButtonItem = editButtonItem()
//leftButtonItem.tintColor = QorumColors.ThemeWhite
navigationItem.leftBarButtonItem = leftButtonItem
} else {
navigationItem.leftBarButtonItem = nil
}
}
public override func setEditing(editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if editing {
navigationItem.leftBarButtonItem?.title = "Delete"
//navigationItem.leftBarButtonItem?.tintColor = QorumColors.Nickname
displaySelectionButtons = true
title = "Delete Photos"
let gridController = valueForKey("_gridController") as! MWGridViewController
gridController.selectionMode = displaySelectionButtons
gridController.collectionView!.reloadData()
} else {
let nav = self.navigationController as! TempPresentVC
let photosToDelete = nav.selectedPhotos
let afterButtonPress = {
//self.navigationItem.leftBarButtonItem?.tintColor = QorumColors.ThemeWhite
self.displaySelectionButtons = false
self.updateNavigation()
let gridController = self.valueForKey("_gridController") as! MWGridViewController
gridController.selectionMode = self.displaySelectionButtons
gridController.collectionView!.reloadData()
}
guard photosToDelete.count > 0 else {
afterButtonPress()
return
}
let title = "Delete Photo"
let message = "Are you sure you want to delete these photos?"
let action = "Delete"
let cancelAction = "Cancel"
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
let declineButton = UIAlertAction(title: cancelAction, style: .Default, handler: { (action: UIAlertAction) in
afterButtonPress()
})
let acceptButton = UIAlertAction(title: action, style: .Default, handler: { (action: UIAlertAction) in
afterButtonPress()
})
alert.addAction(declineButton)
alert.addAction(acceptButton)
UIApplication.topMostController().presentVC(alert) //private lib
}
}
}
class TempPresentVC: UINavigationController, MWPhotoBrowserDelegate {
var selectedPhotos = [Int]()
func photoBrowser(photoBrowser: MWPhotoBrowser!, photoAtIndex index: UInt, selectedChanged selected: Bool) {
if selected {
selectedPhotos.append(index.toInt)
} else {
selectedPhotos.removeObject(index.toInt)
}
}
}
This doesn't allow you to delete a photo in a single photo view mode, but starting from this code base that part would be easy to implement.
Beware, some of the stuff I use inside are private functions I wrote in my other classes, what they do should be clear though.