I have a function "addDoneButton" in FirstViewController.swift, I don't want to copy and paste into the SecondViewController.swift, so I want to call it in the SecondViewController.
func addDoneButton() {
let keyboardToolbar = UIToolbar()
keyboardToolbar.sizeToFit()
let flexBarButton = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace,
target: nil, action: nil)
let doneBarButton = UIBarButtonItem(barButtonSystemItem: .Done,
target: view, action: #selector(UIView.endEditing(_:)))
keyboardToolbar.items = [flexBarButton, doneBarButton]
for textField in self.collectionOfTextField! as [UITextField] {
textField.keyboardType = .DecimalPad
textField.inputAccessoryView = keyboardToolbar
}
}
How to realise it? Thanks in advance from a new swifter.
How about creating an extension of UIViewController and passing the collectionTextFields as an argument?
extension UIViewController {
func addDoneButton(collectionTextFields: [UITextField]) {
let keyboardToolbar = UIToolbar()
keyboardToolbar.sizeToFit()
let flexBarButton = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace,
target: nil, action: nil)
let doneBarButton = UIBarButtonItem(barButtonSystemItem: .Done,
target: view, action: #selector(UIView.endEditing(_:)))
keyboardToolbar.items = [flexBarButton, doneBarButton]
for textField in collectionTextFields {
textField.keyboardType = .DecimalPad
textField.inputAccessoryView = keyboardToolbar
}
}
}
You can make a protocol, then put this method in a protocol extension where self: UIViewController. Then any UIViewController subclass that you want to enable this function, just add the CanAddDoneButton to the protocols it conforms to.. with the caveat that it already has that collectionTextFields variable. Although I think you can even put that variable into the protocol extension, unless it's an IBOutlet.
protocol CanAddDoneButton {
var collectionTextFields: [UITextField]
func addDoneButton()
}
extension CanAddDoneButton where Self: UIViewController {
func addDoneButton() { .... }
}
I think EridB answer is a good way.
Alternatively what about a Swift 2 protocol extension? Its similar to his answer with the only difference being that not all ViewControllers get access to the method unless you conform to the protocol.
protocol Button { }
extension Button where Self: UIViewController {
func addDoneButton(forCollectionTextFields collectionTextFields: [UITextField]) {
let keyboardToolbar = UIToolbar()
keyboardToolbar.sizeToFit()
let flexBarButton = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace,
target: nil, action: nil)
let doneBarButton = UIBarButtonItem(barButtonSystemItem: .Done,
target: view, action: #selector(UIView.endEditing(_:)))
keyboardToolbar.items = [flexBarButton, doneBarButton]
for textField in collectionTextFields {
textField.keyboardType = .DecimalPad
textField.inputAccessoryView = keyboardToolbar
}
}
}
Than in your viewControllers that need to call this method you conform to the protocol and just call it.
class SomeViewController: UIViewController, Button {
override func viewDidLoad() {
super.viewDidLoad()
addDoneButton(forCollectionTextFields: self.collectionOfTextField)
}
}
Hope this helps
Just try this in your SecondViewController.swift:
let firstViewController = FirstViewController()
And any times you need the function just call:
firstViewController.addDoneButton()
Related
I want to set share button on rightbarbuttonitem of navigation controller.
I don't want to add custom images , I want use share button image provided by Xcode.This is how I do in storyboard.
I set Style , and set System_Item as Action.
Now the question is how do I set System_Item programatically, if I create barbuttonitem programatically ?
let shareButton = UIBarButtonItem(title: "",
style: .plain,
target: self,
action: #selector(shareAction(sender:)))
shareButton.tintColor = AppColor.barButtonColor
navigationItem.rightBarButtonItem = shareButton
you need to use UIBarButtonSystemItemAction option to get share action directly
let share = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareAction(sender:)))
shareButton.tintColor = AppColor.barButtonColor
navigationItem.rightBarButtonItem = share
and handle the action as like
#objc func shareAction(sender: UIBarButtonItem) {
}
You can create an extension to do this ->
public extension UIViewController {
func setRightBarButtonItem(tintColor: UIColor = AppColor.barButtonColor) {
let button = UIButton(type: .system)
button.tintColor = tintColor
button.setImage(UIImage(.action), for: .normal) // <-- Set system icon to button
button.translatesAutoresizingMaskIntoConstraints = false
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tap))
button.addGestureRecognizer(tapGesture)
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: button)
}
#objc func tap() {
// Do stuff
}
}
Then in your ViewController
override func viewDidLoad() {
super.viewDidLoad()
setRightBarButtonItem()
// OR
setRightBarButtonItem(tintColor: .blue)
}
UIImage extension to use system icons without version control
extension UIImage {
public convenience init?(_ systemItem: UIBarButtonItem.SystemItem) {
guard let sysImage = UIImage.imageFrom(systemItem: systemItem)?.cgImage else {
return nil
}
self.init(cgImage: sysImage)
}
private class func imageFrom(systemItem: UIBarButtonItem.SystemItem) -> UIImage? {
let sysBarButtonItem = UIBarButtonItem(barButtonSystemItem: systemItem, target: nil, action: nil)
let toolBar = UIToolbar()
toolBar.setItems([sysBarButtonItem], animated: false)
toolBar.snapshotView(afterScreenUpdates: true)
if let buttonView = sysBarButtonItem.value(forKey: "view") as? UIView{
for subView in buttonView.subviews where subView is UIButton {
let button = subView as! UIButton
let image = button.imageView!.image!
return image
}
}
return nil
}
}
This is a bit different question than the previously asked. So be nice.
The entire app has a navigation bar with different colors.
The entire app has few options in its navigation bar. They all are same.
What am I expecting?
I have created this extension for UINavigationController and able to change the navigation bar's background color as per the view controller I will be.
extension UINavigationController {
func updateNavigationBar(withViewControllerID identifier: String?) {
setupNavigationBarButtons()
if identifier == kFirstVCIdentifier {
self.navigationBar.tintColor = .white
self.navigationBar.barTintColor = .red
} else if identifier == kSecondVCIdentifier {
self.navigationBar.tintColor = .white
self.navigationBar.barTintColor = .green
} else if identifier == kThirdVCIdentifier {
self.navigationBar.tintColor = .white
self.navigationBar.barTintColor = .blue
}
self.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
self.navigationBar.isTranslucent = false
self.navigationBar.clipsToBounds = false
self.navigationBar.shadowImage = UIImage.init()
self.view.backgroundColor = .clear
}
internal func setupNavigationBarButtons() {
let barButtonItemSettings = UIBarButtonItem.init(title: "Settings", style: .plain, target: self, action: #selector(actionSettings))
let barButtonItemSpace = UIBarButtonItem.init(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let barButtonItemLogOut = UIBarButtonItem.init(title: "LogOut", style: .plain, target: self, action: #selector(actionLogOut))
let buttons = [barButtonItemSettings, barButtonItemSpace, barButtonItemLogOut]
self.navigationItem.rightBarButtonItems = buttons
}
#objc internal func actionSettings() {
print("Settings")
}
#objc internal func actionLogOut() {
print("LogOut")
}
}
then, I am trying to add UIBarButton within that extension but they are not showing up. So I need to fix it. And if it will be visible, how do I get its actions call so that I can handle UIBarButton's touch event in the entire app. Properly? No patch, please.
I am using it like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.updateNavigationBar(withViewControllerID: self.restorationIdentifier?)
}
Try this and Make extension for UINavigationItem.
extension UINaviationItem {
func setupNavigationBarButtons()
{
let barButtonItemSettings = UIBarButtonItem.init(title: "Settings", style: .plain, target: self, action: #selector(actionSettings))
let barButtonItemSpace = UIBarButtonItem.init(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let barButtonItemLogOut = UIBarButtonItem.init(title: "LogOut", style: .plain, target: self, action: #selector(actionLogOut))
let buttons = [barButtonItemSettings, barButtonItemSpace, barButtonItemLogOut]
self.rightBarButtonItems = buttons
}
#objc func actionSettings() {
print("Settings")
if let current = UIApplication.shared.delegate?.window
{
var viewcontroller = current!.rootViewController
if(viewcontroller is UINavigationController){
viewcontroller = (viewcontroller as! UINavigationController).visibleViewController
print( "currentviewcontroller is %#/",viewcontroller )
}
}
}
#objc func actionLogOut() {
print("LogOut")
}
}
self.navigationController?.updateNavigationBar(withViewControllerID: "second")
self.navigationItem.setupNavigationBarButtons()
I have been on this for awhile now and no matter what I look up I can't seem to understand this. Suppose I'm extending UIView to add a toolbar on keyboard, I am not entirely sure how to pass parameters I need to access from the class that is using that UIView so that I can perform actions upon tapping on the newly created toolbar items in that extension.
Say I have implemented a UITextView in a class:
class MyClass : UIViewController {
var paramToAcess : String! //param to access in extension
override func viewDidLoad() {
super.viewDidLoad()
let textView = UITextView()
textView.addToolBar() //using the extension to add toolbar
self.view.addSubview(textView)
}
//////// other methods ///////
}
and here is the extension:
extension UITextView {
func addToolBar(){
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
toolBar.backgroundColor = UIColor.white
toolBar.tintColor = UIColor.gray
let doneButton = UIBarButtonItem(title: "Save", style: UIBarButtonItemStyle.plain, target: self, action: #selector(donePressed))
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "Cancel", style: UIBarButtonItemStyle.plain, target: self, action: #selector(cancelPressed))
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
toolBar.sizeToFit()
self.inputAccessoryView = toolBar
}
func donePressed(){
self.resignFirstResponder()
}
func cancelPressed(){
self.resignFirstResponder()
//how to access parameters in MyClass to do stuff here?
}
}
I know that extensions cannot have stored property, I have tried to use a protocol but cannot implement it properly. Appreciate any help.
Since you cannot have stored properties as part of a class extension you might find it easier to implement this by subclassing:
class MyTextViewWithToolbar: UITextView {
...
}
Then you can include whatever properties you need in the new class.
Basically I think that a class extension is not the best way to implement this requirement, but subclassing will let you do what you have tried above.
I have the following code and the doneButton handler is never invoked. Any idea what might be going wrong?
I have tried many different formats for the selector, also with and without parameters. I also made sure that the UIView received touch events by manually setting userInteractionEnabled = true. Thanks for any help in advance!
*** UPDATE: I have refactored the code completely, but same behaviour:
My Protocol:
import Foundation
protocol CustomDatePickerDelegate {
func dataPickerDone(date: NSDate)
func dataPickerChanged(date: NSDate)
}
my class:
import Foundation
import UIKit
class CustomDatePicker{
var delegate: CustomDatePickerDelegate?
var datePicker : UIDatePicker = UIDatePicker()
func CreateDatePicker(sender: UITextField) {
if (delegate is UIViewController)
{
let _self = delegate as! UIViewController
let _view = UIView(frame: CGRectMake(0, 0, _self.view.frame.width, 220))
let datePickerView : UIDatePicker = UIDatePicker(frame: CGRectMake(0, 40, 0, 0))
datePickerView.datePickerMode = UIDatePickerMode.Date
let _toolbar = UIToolbar()
let _doneButton = UIBarButtonItem(title: "DONE", style: UIBarButtonItemStyle.Done, target: self, action: #selector(CustomDatePicker.doneButton))
_toolbar.translucent = false
_toolbar.sizeToFit()
_doneButton.accessibilityLabel = "DoneToolbar"
let _spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
_toolbar.setItems([_spaceButton, _doneButton], animated: false)
_toolbar.userInteractionEnabled = true
datePickerView.addTarget(self, action: #selector(CustomDatePicker.handleDatePicker), forControlEvents: UIControlEvents.ValueChanged)
_view.addSubview(datePickerView)
_view.addSubview(_toolbar)
sender.inputView = _view
}
}
#objc func handleDatePicker(sender:UIDatePicker) {
delegate?.dataPickerChanged(datePicker.date)
}
#objc func doneButton(){
delegate?.dataPickerDone(datePicker.date)
}
}
How I use it from the ViewController:
let _dataPicker : CustomDatePicker = CustomDatePicker()
_dataPicker.delegate = self
_dataPicker.CreateDatePicker(dataUI)
func dataPickerDone(date: NSDate) {
dataUI.text = DataFormatejada(date)
dataUI.resignFirstResponder()
}
func dataPickerChanged(date: NSDate) {
dataUI.text = DataFormatejada(date)
}
I found a way to resolve the problem after many hours of try-error. The problem is with the target:self reference in the lines:
let _doneButton = UIBarButtonItem(title: "DONE", style: UIBarButtonItemStyle.Done, target: self, action: #selector(CustomDatePicker.doneButton))
and
_toolbar.userInteractionEnabled = true
datePickerView.addTarget(self, action: #selector(CustomDatePicker.handleDatePicker), forControlEvents: UIControlEvents.ValueChanged)
so what I have done is to create a new property in MyDatePicker class:
var instance : CustomDatePicker?
change those addTarget methods as:
let _doneButton = UIBarButtonItem(title: "DONE", style: UIBarButtonItemStyle.Done, target: instance, action: #selector(CustomDatePicker.doneButton))
_toolbar.userInteractionEnabled = true
datePickerView.addTarget(instance, action: #selector(CustomDatePicker.handleDatePicker), forControlEvents: UIControlEvents.ValueChanged)
and I use it from the ViewController as:
let _dataPicker : CustomDatePicker = CustomDatePicker()
_dataPicker.delegate = self <---- **this made the trick**
_dataPicker.instance = _dataPicker
_dataPicker.CreateDatePicker(dataUI)
but this is not elegant and it is a patch for a more fundamental issue. Why self is not providing the instance of the object and I have to manually pass it back when I create the object?
thanks!
I had created button in toolbar and set tag to it. I had declared that button locally. Now in some other function I want to disable the button as per condition.
Is there any way to disable UIBarButton based on tag without declaring the button globally.
****************************************************
func setToolBar()
{
let toolbar : UIToolbar = UIToolbar()
toolbar.sizeToFit()
let prevButton = UIBarButtonItem(title: "<", style: .Plain, target: self, action:"goBack:")
prevButton.tag = 20
let nextButton = UIBarButtonItem(title: ">", style: .Plain, target: self, action:"goNext:")
nextButton.tag = 30
let flexButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .Plain, target: self, action:"done:")
let arrItems : NSArray = [prevButton , nextButton,flexButton, doneButton]
[toolbar.setItems(arrItems as? [UIBarButtonItem], animated: true)]
city.inputAccessoryView = toolbar
birthdate.inputAccessoryView = toolbar
}
****************************************************
func goBack(sender: UIBarButtonItem)
{
let prevTag : NSInteger = CurrentTextFiled.tag - 1
let nextResponder = CurrentTextFiled.superview?.viewWithTag(prevTag)
if((nextResponder) != nil)
{
nextResponder!.becomeFirstResponder()
}
else
{
//Disable UIBarButton here with tag 20
}
}
****************************************************
func goNext(sender: UIBarButtonItem)
{
let prevTag : NSInteger = CurrentTextFiled.tag + 1
let nextResponder = CurrentTextFiled.superview?.viewWithTag(prevTag)
if((nextResponder) != nil)
{
nextResponder!.becomeFirstResponder()
}
else
{
//Disable UIBarButton here with tag 30
}
}
I distilled this down to a code snippet that can be run inside a playground, and verified this within Xcode.
In essence, you want to give a tag to the toolbar and find it with viewWithTag(). Once you have the toolbar, traverse thru its items array and filter out the button you want to manipulate. Don't forget to add the toolbar to your viewController's view as a subview.
import UIKit
import XCPlayground
class MyViewController: UIViewController {
override func viewDidLoad() {
self.setToolBar()
self.disableButton(20)
}
func setToolBar()
{
let toolbar : UIToolbar = UIToolbar()
toolbar.tag = 10
toolbar.sizeToFit()
self.view.addSubview(toolbar) //must add to subview of viewcontroller
let prevButton = UIBarButtonItem(title: "<", style: .Plain, target: self, action:"goBack:")
prevButton.tag = 20
let nextButton = UIBarButtonItem(title: ">", style: .Plain, target: self, action:"goNext:")
nextButton.tag = 30
let flexButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .Plain, target: self, action:"done:")
let arrItems : NSArray = [prevButton, nextButton,flexButton, doneButton]
[toolbar.setItems(arrItems as? [UIBarButtonItem], animated: true)]
print("Toolbar set.")
}
func disableButton(tag: Int)
{
if let toolbarWithButtons = self.view.viewWithTag(10) as? UIToolbar {
print("Toolbar found by tag. Trying to disable button with tag \(tag).")
var buttonToDisable: Array<AnyObject>?
if let buttons = toolbarWithButtons.items {
buttonToDisable = buttons.filter({
(x : AnyObject) -> Bool in
if let button = x as? UIBarButtonItem {
if button.tag == tag {
return true
}
}
return false
})
if let button = (buttonToDisable!.first as? UIBarButtonItem){
button.enabled = false
print("Button with tag \(button.tag) enabled: \(button.enabled)")
}
}
}
else {
print("Toolbar not found by tag.")
}
}
}
var ctrl = MyViewController()
XCPShowView("Playground VC", ctrl.view)
I hope this is helpful!
You can get view a using tag.
if let button = self.view.viewWithTag(YOUR_TAG_HERE) as? UIBarButtonItem {
button.enabled = false
}
Instances of UIView have a method to find sub-views based on the tag:
func viewWithTag(_ tag: Int) -> UIView?
I'm pretty sure this will search deeply, so, as long as you can get to a view near the top of the hierarchy, this should work for you.
If some one still need this:
In case where you will have navigationController, so you can access to your UIBarButtonItem like so
if let barButtonItem = navigationItem.rightBarButtonItems?.first(where: {$0.tag == <YOUR_TAG> }) {
// Do staff here
}
and in case with toolBar situation is similar:
if let barButtonItem = toolBar.items?.first(where: {$0.tag == <YOUR_TAG>}) {
// Do staff here
}