How to make globe method for custom button action in swift - ios

I have a sign in screen in which two fields are there password and email textfield.
I added both textfield with stackview. After adding textfields, I need to add button of password lock. After clicking on password lock button user can see the password. I took button from library and try to add on textfield but, due to stackview it always set as third element for stackview. So I am unable to add.
So I decided to add with extension of textfield. So i added button successfully in utility class,
but unable to add action with selector. It showing an error
Value of type 'UIViewController' has no member 'passwordAction'
My code for add button is
extension UITextField {
func passwordButton(vc:UIViewController){
let paddingView = UIView(frame: CGRect(x: 25, y: 25, width: 24, height: 17))
let passwordButton = UIButton(frame: CGRect(x: 0, y: 0, width: 24, height: 17))
passwordButton.setImage(UIImage(named: "show_password"), for: .normal)
passwordButton.addTarget(vc, action: #selector(vc.passwordAction), for: .touchUpInside)
paddingView.addSubview(passwordButton)
self.addSubview(paddingView)
}
}
class Loginviewcontroller{
passwordTextField.passwordButton(vc: self)
func passwordAction(){
print("action")
}
}
I am calling this method from login controller.
So I have two question:-
when two textfield are attached with stackview, we can not put button on textfield with storyboard?
How can I make globle method to add button and add action that can access in uiviewcontroller?

Error is self explanatory, you added the passwordAction method to Loginviewcontroller but in func passwordButton you take a UIViewController as an argument. As a result even when you pass instance of Loginviewcontroller to passwordButton function call, in the function scope vc is just another UIViewController and clearly all UIViewControllers does not have a method named passwordAction.
As you said you wanna make global method to add button and add action that can access in uiviewcontroller you can follow below approach
Step 1: Declare a protocol
#objc protocol SecureTextProtocol where Self: UIViewController {
func passwordAction()
}
Step 2: Update your passwordButton method to take SecureTextProtocol instead of plain UIViewController
extension UITextField {
func passwordButton(vc:SecureTextProtocol){
let paddingView = UIView(frame: CGRect(x: 25, y: 25, width: 24, height: 17))
let passwordButton = UIButton(frame: CGRect(x: 0, y: 0, width: 24, height: 17))
passwordButton.setImage(UIImage(named: "show_password"), for: .normal)
passwordButton.addTarget(vc, action: #selector(vc.passwordAction), for: .touchUpInside)
paddingView.addSubview(passwordButton)
self.addSubview(paddingView)
}
}
Step 3: Make your ViewController's that want to get the button action to confirm to SecureTextProtocol protocol
class Loginviewcontroller: UIViewController, SecureTextProtocol {
passwordTextField.passwordButton(vc: self)
func passwordAction(){
print("action")
}
}
That should do the job

You can always solve this problem with simple code, that's the way I do it.
extension UITextField {
func setPasswordToggleImage(_ button: UIButton) {
if(isSecureTextEntry){
button.setImage(UIImage(named: "ic_password_visible"), for: .normal)
}else{
button.setImage(UIImage(named: "ic_password_invisible"), for: .normal)
}
}
func enablePasswordToggle(){
let button = UIButton(type: .custom)
setPasswordToggleImage(button)
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -16, bottom: 0, right: 0)
button.frame = CGRect(x: CGFloat(self.frame.size.width - 25), y: CGFloat(5), width: CGFloat(25), height: CGFloat(25))
button.addTarget(self, action: #selector(self.togglePasswordView), for: .touchUpInside)
self.rightView = button
self.rightViewMode = .always
}
#objc func togglePasswordView(_ sender: Any) {
self.isSecureTextEntry = !self.isSecureTextEntry
setPasswordToggleImage(sender as! UIButton)
}
}
then on viewcontroller's viewdidload just call the functions on your storyboards
override func viewDidLoad() {
super.viewDidLoad()
txtPassword.enablePasswordToggle()
txtConfirmPassword.enablePasswordToggle()
}

Related

UIbutton inside UIViewControllers inside UIScrollView not getting triggered on touch

I'v added two UIViewControllers inside of a UIScrollView, and now I'm trying to add targets to the button inside of the UIViewControllers, I've checked the internet but none of those solutions worked for me, this is what I'm doing
inside of the main ViewController
scrollView.contentSize = CGSize(width: view.frame.width * 2, height:0)
let postsViewController = PostsViewController()
postsViewController.scrollView = scrollView
postsViewController.view.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
let chatsViewController = ChatsViewController()
chatsViewController.scrollView = scrollView
chatsViewController.view.frame = CGRect(x: view.frame.width, y: 0, width: view.frame.width, height: view.frame.height)
scrollView.addSubview(postsViewController.view)
scrollView.addSubview(chatsViewController.view)
postsViewController.didMove(toParent: self)
chatsViewController.didMove(toParent: self)
then inside the postsViewController I'm doing this
let a = UIButton()
a.setImage(currentTheme.chat, for: .normal)
a.imageEdgeInsets = UIEdgeInsets(top: 1.5, left: 0.0, bottom: 1.5, right: 0.0)
view.addSubview(a)
view.addConstraint("V:|-200-[v0(80)]-0-|",a)
view.addConstraint("H:|-200-[v0(80)]-0-|",a)
a.addTarget(self, action: #selector(abc), for: .touchUpInside)
the add "addConstraint" is an extension function just to add constraint to views
the abc function
#objc func abc() {
print("hello")
}
you can see the scrollView setting here
https://ibb.co/ZB1LJsy
Try to initialize your button like this
let a : UIButton = UIButton(type: .custom)
And add this after to the initialize of your UIScrollView
scrollView.delaysContentTouches = false
I tested in an xcode project. I can not execute the function (button) in the other UIViewController.
By cons if you sent the #selector it works.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let scrollView : UIScrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width))
scrollView.delaysContentTouches = false
view.addSubview(scrollView)
let TestViewController = TestViewController1()
TestViewController.select = #selector(demande(sender:))
TestViewController.view.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
scrollView.addSubview(TestViewController.view)
print("dd")
}
#objc func demande(sender: UIButton!) {
print("button click", sender.tag)
}
}
class TestViewController1: UIViewController {
var select : Selector!
override func viewDidLoad() {
super.viewDidLoad()
let button : UIButton = UIButton(type: .custom)
button.setImage(#imageLiteral(resourceName: "turn_off_on_power-512"), for: .normal)
button.tag = 2
button.addTarget(self, action: select, for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
view.addSubview(button)
}
}
Hoping that it helps you
okay, I solved my problem, the reason why the button wasn't getting trigger is because I forgot to attach the viewController of the view to the scrollview, so all I had to do is to attach the viewController as well and it worked.
self.addChild(postsViewController)
self.addChild(chatsViewController)
now it is working.

Fixing "use of unresolved identifier 'addTarget'" while adding func to button click event

I've created a UIButton programmatically as shown below:
let buttons: [UIButton] = [UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))];
Now if I try to add a function to it programmatically like this:
[buttons[0] addTarget:self action:#selector(buttonClicked:)forControlEvents:UIControlEventTouchUpInside]
I get an error saying that addTarget is not defined.
How do I fix this?
you are try to use the Objective-C syntax in swift, this is entirely wrong, use your code as like
buttons.first?.addTarget(self, action: #selector(self.buttonClicked(_:)), for: .touchUpInside)
and handle the action as like
#objc func buttonClicked( _ sender: UIButton) {
print("buttonClicked Action Found")
}
Ref : Apple Document for UIButton
First of all you are creating [UIButton] which is Array of UIButton and it's not a single Button.
You can not create Array of UIButton that way. You will need a for loop for that and you need to update the frame accordingly.
And you can create a single UIButton this way:
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
then you can add it into the UIView this way:
self.view.addSubview(button)
Without above line it your button will not show into your screen.
Next if you want to add action to that button you can do it by adding this line in your button code:
button.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)
and it will need a helper method which will execute when button will click.
#objc func buttonClicked(_ sender: UIButton) {
//Perform your action when button is clicked.
}
And you also need to apply backgroundColor and setTitle to the button.
and your final code will look like:
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button.backgroundColor = UIColor.green
button.setTitle("Test Button", for: .normal)
button.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)
self.view.addSubview(button)

How can I override this class function?

I have an extension class, where I'm extending UIButton like below, it's working fine.
extension UIButton {
class func backButtonTarget(_ target: Any, action: Selector) -> UIBarButtonItem {
let backButton = UIButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(58), height: CGFloat(15)))
backButton.setTitle("Cancel",for: .normal)
let barBackButtonItem = UIBarButtonItem(customView: backButton)
backButton.addTarget(target, action: action, for: .touchUpInside)
return barBackButtonItem
}
}
But now, I need to change its title for some view controller, so I was thinking of its overriding, but failed. How can be this be overridden, so that I can change its title?
Extensions can not/should not override.
It is not possible to override functionality (like properties or methods) in extensions as documented in Apple's Swift Guide.
Extensions can add new functionality to a type, but they cannot override existing functionality.
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Extensions.html
You can subclass the bar button Item like
class CustomBarButtonItem: UIBarButtonItem {
override func awakeFromNib() {
super.awakeFromNib()
customize()
}
func customize() {
frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(58), height: CGFloat(15))
// Add more as per requirement
}
}
Make title a String parameter for the function, so you can call it and provide a custom title.
class func backButtonTarget(_ target: Any, action: Selector, title: String) -> UIBarButtonItem {
let backButton = UIButton(frame: CGRect(x: 0, y: 0, width: 58, height: 15))
backButton.setTitle(title, for: .normal)
let barBackButtonItem = UIBarButtonItem(customView: backButton)
backButton.addTarget(target, action: action, for: .touchUpInside)
return barBackButtonItem
}
}

Why is UIView removed from heirarchy after presenting view controller?

Ok I just ran into something bizarre. I've got my app controller dependency injecting a view (header) into a view controller. That view controller presents another view controller modally and dependency injects it's own header for the presenting view controller to use. But when its presented the header from the first controller disappears.
The property is still set but it's been removed from the view hierarchy.
I've reproduced this issue in fresh singleview project:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let button = UIButton(frame: CGRect(x: 0, y: 20, width: 100, height: 50))
button.setTitle("Click Me!", for: .normal)
button.addTarget(self, action: #selector(self.segue), for: .touchUpInside)
button.backgroundColor = .black
button.setTitleColor(.lightGray, for: .normal)
self.view.addSubview(button)
}
func segue() {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.backgroundColor = .lightGray
let firstVC = FirstViewController()
firstVC.sharedView = view
present(firstVC, animated: false)
}
}
class FirstViewController: UIViewController {
var sharedView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(self.sharedView)
let button = UIButton(frame: CGRect(x: 0, y: 200, width: 100, height: 50))
button.setTitle("Click Me!", for: .normal)
button.addTarget(self, action: #selector(self.segue), for: .touchUpInside)
button.backgroundColor = .black
button.setTitleColor(.lightGray, for: .normal)
self.view.addSubview(button)
}
func segue() {
let secondVC = SecondViewController()
secondVC.sharedView = self.sharedView
present(secondVC, animated: true)
}
}
class SecondViewController: UIViewController {
var sharedView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(self.sharedView)
let button = UIButton(frame: CGRect(x: 0, y: 200, width: 100, height: 50))
button.setTitle("Click Me!", for: .normal)
button.addTarget(self, action: #selector(self.segue), for: .touchUpInside)
button.backgroundColor = .black
button.setTitleColor(.lightGray, for: .normal)
self.view.addSubview(button)
}
func segue() {
self.dismiss(animated: true)
}
}
Can someone explain what's going on here? Why does the sharedView disappear from the FirstViewController?
At the doc of -addSubview(_:):
Views can have only one superview. If view already has a superview and that view is not the receiver, this method removes the previous
superview before making the receiver its new superview.
That should explain your issue.
I'd suggest instead that you create a method that generate a headerView (a new one each time) according to your customization style.
If you really want to "copy" the view, you can check that answer. Since UIView is not NSCopying Compliant, their trick is to "archive/encode" it since it's NSCoding compliant, copy that archive, and "unarchive/decode" the copy of it.

Eliminate repeated code Swift 3

I have multiple navigation controllers and their root view controllers in my app. I want each navigation bar to have social media buttons closely placed on the right side of the bar. For the same I have used this code to show the buttons in 1 view controller:
let fbImage = UIImage(named: "Facebook.png")!
let twitterImage = UIImage(named: "Twitter.png")!
let youtbImage = UIImage(named:"YouTube.png")!
let fbBtn: UIButton = UIButton(type: .custom)
fbBtn.setImage(fbImage, for: UIControlState.normal)
fbBtn.addTarget(self, action: #selector(HomeViewController.fbBtnPressed), for: UIControlEvents.touchUpInside)
fbBtn.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
let fbBarBtn = UIBarButtonItem(customView: fbBtn)
let twitterBtn: UIButton = UIButton(type: .custom)
twitterBtn.setImage(twitterImage, for: UIControlState.normal)
twitterBtn.addTarget(self, action: #selector(HomeViewController.twitterBtnPressed), for: UIControlEvents.touchUpInside)
twitterBtn.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
let twitterBarBtn = UIBarButtonItem(customView: twitterBtn)
let youtbBtn: UIButton = UIButton(type: .custom)
youtbBtn.setImage(youtbImage, for: UIControlState.normal)
youtbBtn.addTarget(self, action: #selector(HomeViewController.youtubeBtnPressed), for: UIControlEvents.touchUpInside)
youtbBtn.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
let youtbBarBtn = UIBarButtonItem(customView: youtbBtn)
self.navigationItem.setRightBarButtonItems([youtbBarBtn, twitterBarBtn, fbBarBtn], animated: false)
Now I want the same buttons on all navigations bars. I can easily copy this code and respective target methods in viewDidLoad() of each view controller, but too much code is getting repeated. So how can avoid this situation?
I am using Swift 3. I am new to iOS. Any help will be appreciated!
Most duplications are solved by using functions. The first step is to extract that code into a function, the second step is to use the same function from multiple places.
You can add it to an extension, for example:
extension UIViewController {
func addShareButtons() {
...
self.navigationItem.setRightBarButtonItems([youtbBarBtn, twitterBarBtn, fbBarBtn], animated: false)
}
}
and only call
self.addShareButtons()
from every controller that needs the buttons.
You can add button handlers to the extension too.
Another method is to use a UIViewController subclass but that's always a problem if you want to use a UITableViewController subclass.
You can design a custom navigation controller and add these code to the navigation controller. inherit the navigation controller to your storyboard or programmatically where you want to use.
//sample code
class "YourNavigationCorollerName": UINavigationController, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//declare here you code, what you want
}
}
Assuming that these buttons ALWAYS have the same behavior, you can create a custom class for them, and place the repeated code there. For instance:
class FacebookButton : UIButton {
override init() {
super.init()
self.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
self.setImage(UIImage(named: "Facebook.png")!, for: .normal)
}
}
...I think the compiler will yell at you for initializing a UIView without a decoder and frame, but hopefully you get the idea.
To improve on this, you could 1) create a custom UINavigationController with the UIBarButtonItems you want to use, and reuse that controller, and/or 2) create a protocol like the following:
protocol FacebookBtnHandler {
func fbBtnPressed()
}
...and have any relevant VCs conform to it. This would allow you to assign the target and selector for the button in the FacebookButton init method, where you assign the image and frame, and hence prevent repetition of that line, as well.
Try to implement bar button by creating subclass of UIBarButton
class Button: UIBarButtonItem {
convenience init(withImage image : UIImage,Target target: Any, andSelector selector: Any?){
let button = UIButton(type: .custom)
button.setImage(image, for: UIControlState.normal)
button.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
button.addTarget(target, action: selector, for: UIControlEvents.touchUpInside)
self.init(customView: button)
}
}
let fbImage = UIImage(named: "Facebook.png")!
let twitterImage = UIImage(named: "Twitter.png")!
let youtbImage = UIImage(named:"YouTube.png")!
let fbBtn = Button(withImage: fbImage, Target: self, andSelector: #selector(HomeViewController.fbBtnPressed))
let twitterBtn = Button(withImage: fbImage, Target: self, andSelector: #selector(HomeViewController.twitterBtnPressed))
let youtubeBtn = Button(withImage: fbImage, Target: self, andSelector: #selector(HomeViewController.youtubeBtnPressed))
self.navigationItem.setRightBarButtonItems([youtbBarBtn, twitterBarBtn, fbBarBtn], animated: false)
and make it for all your view controller
extension UIViewController {
func addButtons() {
// add above code
}
}

Resources