"Use of unresolved indentifier 'self'" with button.addTarget - ios

I am a beginner to Swift. I have created a very simple button component, but when I try to add a function that is tied to the button click, I get the error: "Use of unresolved identifier 'self.'" Here is my code:
import UIKit
import PlaygroundSupport
let containerView = UIView (frame: CGRect(x: 0, y: 0, width: 600, height: 600))
containerView.backgroundColor = UIColor.blue
let button = UIButton (frame: CGRect(x: 100, y: 100, width: 200, height: 200))
button.backgroundColor = UIColor.yellow
button.setTitle("Test Button", for: .normal)
button.setTitleColor(.red, for: .normal)
button.layer.cornerRadius = 10
func ButtonClicked(_ sender: UIButton) {
print()
}
button.addTarget(self, action: #selector(ButtonClicked), for: .touchUpInside)
containerView.addSubview(button)
PlaygroundPage.current.liveView = containerView
Any help would be appreciated greatly, thanks!

As mentioned in the comments, there is no self to reference in your code as you're not working within a class or struct. The first thing you'll want to do is to wrap this stuff up into a class, and reorganize it a bit. I'd suggest going back to Xcode and doing the following:
File > New > Playground…
Select iOS > Single View
Save this new Playground somewhere.
Now, you should have a simple Playground created from a template. Here, you'll be able to see how a new class called MyViewController that inherits from UIViewController is created. Within that, you'll find a function called loadView(). Then, outside the class, you'll see where this new view controller is created and placed into the PlaygroundPage's current liveView.
Ideally, you should be able to click the Play button at the bottom of the code view, and if the Assistant editor is open, you'll see a simple view with a "Hello World!" label in the center of the view. (The assistant button is the one with the two interlocking circles.)
Assuming that runs and works for you, then I'd suggest you start adding your code back in and try to follow the patterns setup in the template. Place your code that sets up the view in the loadView() function, and move the ButtonClicked() function outside of that, but still within the class.
With a few modifications, you should be able to get up and running, and start playing around with Swift. As an example, here's a working version of your Playground code:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let containerView = UIView (frame: CGRect(x: 0, y: 0, width: 600, height: 600))
containerView.backgroundColor = UIColor.blue
let button = UIButton (frame: CGRect(x: 100, y: 100, width: 200, height: 200))
button.backgroundColor = UIColor.yellow
button.setTitle("Test Button", for: .normal)
button.setTitleColor(.red, for: .normal)
button.layer.cornerRadius = 10
button.addTarget(self, action: #selector(ButtonClicked), for: .touchUpInside)
containerView.addSubview(button)
self.view = containerView
}
#objc func ButtonClicked(_ sender: UIButton) {
print("ButtonClicked")
}
}
PlaygroundPage.current.liveView = MyViewController()
Have fun, spend some quality time with the Swift Programming Guide, and things'll start to make sense soon enough.

Related

Why the interface is not displayed?

There are 2 functions:
func configure() {
let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
scrollView.contentSize = CGSize(width: view.frame.size.width, height: 1200)
scrollView.backgroundColor = .white
view.addSubview(scrollView)
}
func configure1() {
let listButton = UIButton(frame: CGRect(x: view.frame.size.width - 30, y: 57, width: 25, height: 25))
listButton.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
listButton.setImage(UIImage(systemName: "list.bullet"), for: .normal)
scrollView.addSubview(listButton)
}
When calling these functions, only the scrollView is displayed, but the listButton is not displayed
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
configure()
configure1()
}
If combine the code of these functions into one, then everything works. Why doesn't it work out of two functions?
I have one question about your code, you created the ScrollView directly on Storyboard or directly on the code? If you created directly on code, the steps below should work.
I just paste your code to my Xcode and found some problems.
You did not paste the buttonAction code, so this error appeared:
Cannot find 'buttonAction' in scope
To solve this, I did a simple function that prints in console "buttonPrint".
#objc func buttonAction() {
print("buttonPrint")
}
configure1 function did not find scrollView in scope
To solve this, I created a view controller instance property declaring scrollView at the top of my ViewController, outside other functions, using the code below:
var scrollView = UIScrollView()
Redeclaration of scrollView
You declared scrollView on the first line of configure(). To solve the redeclaration, I simply deleted let statement.
Then I builded the app and it worked with these two functions :)
I'm using Xcode version 13.2.1 and an iPhone 11 simulator.

How to make globe method for custom button action in swift

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()
}

RxSwift not working with UIButton

I am working on RxSwift and started creating few basic. I have added new button however with rx_tap subscribe not working for button action. Below is my code, please let me know what I am doing wrong
let button = UIButton(frame: CGRect(x: 10, y: 66, width: 100, height: 21))
button.backgroundColor = UIColor.redColor()
button.setTitle("Login", forState: UIControlState.Normal)
let disposeBag = DisposeBag()
button.rx_tap
.subscribe { [weak self] x in
self!.view.backgroundColor = UIColor.redColor()
}
.addDisposableTo(disposeBag)
self.view.addSubview(button)
Your subscription is cancelled immediately because of the scope of your DisposeBag. It is created, then goes out of scope and immediately deallocated. You need to retain your bag somewhere. If you are using a view controller or something like that, you can create a property there and assign it to that.

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
}
}

Playground vs. Simulator - Why Does Simple View Render Differently?

Xcode 7.3, iOS 9.3
I am experimenting with playgrounds so as to expand my capacity to use them to do rapid prototyping. I have created a simple view controller which is adding/centering a button to/in its view. I have duplicated the view controller code in a project and in a playground. When I execute the project in the simulator the button is centered. When I execute the playground the button is not centered. Would someone please help me to understand why this is happening?
I have included 3 screenshots:
Xcode Project With View Controller Code
Simulator Showing the Result of Executing the Project
Playground With View Controller Code and Timeline Showing the Execution Result
Per #Paulw11's comment, here is the updated and working view controller code:
class ViewController : UIViewController {
let button = UIButton(type: .System)
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .whiteColor()
view.layer.borderColor = UIColor.redColor().CGColor
view.layer.borderWidth = 2
button.bounds = CGRect(x: 0, y: 0, width: 50, height: 20)
button.backgroundColor = .blueColor()
button.setTitle("Rotate", forState: UIControlState.Normal)
button.addTarget(self, action: #selector(rotate), forControlEvents: UIControlEvents.TouchUpInside)
view.addSubview(button)
print("View: size is \(view.bounds.width) x \(view.bounds.height), centered #\(view.center)")
print("Button: size is \(button.bounds.width) x \(button.bounds.height), centered #\(button.center)")
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
button.center = view.convertPoint(view.center, fromView: view.superview)
}
func rotate() {
print("Rotate")
}
}

Resources