pass custom parameter to uibutton #selector swift 3 - swift

I have a 2 classes where I am passing uistackviews from one class to other. I want the controls to be created in same stackview. Hence I am passing the view in all the render function parameters. I also want that view to be passed with action #selector of uibutton
class 1:
class ViewController: UIViewController {
func createbutton(parentview: UIStackView) {
let buttn = UIButton()
buttn.backgroundColor = .gray
buttn.setTitle("testttt", for: .normal)
buttn.frame.size.height = 30
buttn.frame.size.width = 40
buttn.addTarget(self, action: #selector(anotherbutton(parentview:)), for: .touchUpInside)
parentview.addArrangedSubview(buttn)
}
func anotherbutton(parentview: UIStackView) {
//another button here
}
func loadpage() {
print("loadpage")
}
}
Class 2:
class plugin : UIViewController {
let vw = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
let parentview = getparentnode()
vw.createbutton(parentview: parentview)
}
func getparentnode() -> UIStackView {
let parentnode = UIStackView()
parentnode.axis = UILayoutConstraintAxis.vertical
parentnode.distribution = UIStackViewDistribution.equalSpacing
parentnode.alignment = UIStackViewAlignment.center
parentnode.spacing = 16.0
parentnode.tag = 50
parentnode.translatesAutoresizingMaskIntoConstraints = false;
self.view.addSubview(parentnode)
//Constraints
parentnode.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
parentnode.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
return parentnode
}
}
but this throws an error unrecognized selector sent to instance 0x7b25e010'
How to pass the UIView in action selector parameter ? Thank you for any help

You can't. The only things that you can pass through a selector is:
Nothing
The object itself (in this case the button)
These scenarios would look like this:
button.addTarget(self, action: #selector(myFunc), ...) //no parameters
or
button.addTarget(self, action: #selector(myFunc(_:)) //passes itself (the button)
If you want to pass the value of a view to another ViewController I recommend using the prepareForSegue method. That is how you are supposed to pass data from ViewController to ViewController.
In terms of the rest of your code, I believe you are breaking the MVC design pattern by creating an instance of your class in another class (this line: let vw = ViewController()). First of all, this will create an entirely new instance if your ViewController, which isn't the same as the one running on your device. Second of all, this is bad practice. You should be allowing each viewController to manage itself and not have outwards interference from other viewControllers. Using prepareForSegue is an example of using the MVC design pattern effectively.
Hope this helped.

Related

Push View Controller Swift

I am trying to push to a new view controller when I click the quiz button I implemented. I set it up as below:
quizButton = UIButton()
quizButton.setTitle(" Quiz ", for: .normal)
quizButton.backgroundColor = .lightGray
quizButton.layer.cornerRadius = 9
quizButton.translatesAutoresizingMaskIntoConstraints = false
quizButton.addTarget(self, action: #selector(takeQuiz), for: .touchUpInside)
view.addSubview(quizButton)
and I defined the function takeQuiz as below, where there is a separate view controller called QuizViewController:
#objc func takeQuiz() {
let newViewController = QuizViewController()
navigationController?.pushViewController(newViewController, animated: true)
}
I'm not sure if it was necessary for me to use delegates here, since I am not trying to relay any information from one view controller to the other. Let me know, thanks in advance.

Different behavior between addTarget and addGestureRecognizer

I have a function that creates a button with a selector function as a target. The address of a button gets passed to handleSelectPhoto.
lazy var image1Button = createButton(selector: #selector(handleSelectPhoto))
func createButton(selector: Selector) -> UIButton {
let button = UIButton(type: .system)
button.addTarget(self, action: selector, for: .touchUpInside)
return button
}
#objc func handleSelectPhoto(button: UIButton) {
// Do something with button, this works
}
Now, I am trying to change the class of the above from UIButton to UIImageView like the following,
lazy var image1Button = createButton(selector: #selector(handleSelectPhoto))
func createButton(selector: Selector) -> UIImageView {
let view = UIImageView()
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: selector))
view.isUserInteractionEnabled = true
return view
}
#objc func handleSelectPhoto(button: UIImageView) {
// HERE, button does not get passed
}
With the above changes, in handleSelectPhoto, button instance is not correct. I can not read it as UIImageView type.
If I add a selector function using addGestureRecognizer, does it behave differently than adding a selector function using addTarget, in terms of how selector function is executed with parameters? Maybe I am not understanding how this selector function works...
Adding a target to something like UIGestureRecognizer or UIButton only passes one parameter to the selected function. This parameter depends on the type you are about to add the target on.
In your case the first code snippet works because you are adding a target to an UIButton, so your selected function gets passed this UIButton instance.
In your second scenario you add the target to an UITapGestureRecognizer, so the passed instance will be exactly this gesture recognizer, which cannot be of type UIImageView.
So the difference from the target parameter perspective between UIGestureRecognizer and UIButton is no difference. They both pass their instances to the selected function.
From the UIView subclass perspective there is the difference that UIGestureRecognizer is not a subclass of UIView, but UIButton is. That's why you can just use the passed UIButton instance in your first snippet. In the second snippet you need use the view property of UIGestureRecognizer.
guard let imageView = gestureRecognizer.view as? UIImageView else { return }
Besides your actual question it seems important to clarify how to write #selectors correctly. You're doing it correct already. No change necessary. Some may say you need to add (_:) or : to your selector like so: #selector(handleSelectPhoto(_:)) but this isn't true. In general, you only need to add these special characters when you are selecting a method which has an overload method with a different amount of parameters, but the same base name.
You should make your tell while setting the selection that your function will accept a parameter by adding : at the end of method name.
lazy var image1Button = createButton(selector: #selector(handleSelectPhoto:))
UIKit will automatically understand that the selector methods parameter will be of type UITapGestureRecognizer. Now rewrite the below method like this and you will be good to go.
#objc func handleSelectPhoto(gesture: UITapGestureRecognizer) {
if let buttonImageView = gesture.view as? UIImageView {
//Here you can make changes in imageview what ever you want.
}
}

swift call a func from another viewcontroller

I would like to call a func from another viewcontroller.
here with the code in pubListViewController: It is working fine.
override func viewDidAppear(_ animated: Bool) {
navigationBarTitleImage(imageTitle: "IconTitle")
}
func navigationBarTitleImage(imageTitle: String) {
// 1
// let nav = self.navigationController?.navigationBar
// 2
// nav?.barStyle = UIBarStyle.black
// nav?.tintColor = UIColor.yellow
// 3
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
imageView.contentMode = .scaleAspectFit
// 4
let image = UIImage(named: imageTitle)
imageView.image = image
// 5
navigationItem.titleView = imageView
}
now I try to call it in another viewcontroller as below, but it shows nothing.
override func viewDidAppear(_ animated: Bool) {
pubListViewController().navigationBarTitleImage(imageTitle: "addTitle")
}
When you're using notation like that pubListViewController() you call free empty initializer of pubListViewController which creates new instance of class pubListViewController, but you do already have one in your screens flow I bet, so all changes made by function you're calling later are being applied to invisible instance of pubListViewController.
To solve this problem you need a reference actually displaying instance of pubListViewController from your another viewcontroller
In another viewcontroller you can create a property of type pubListViewController, then before showing another viewcontroller set its property to self, and use that property from wherever you want in another viewcontroller.
class PubListViewController: UIViewController {
func prepareForSegue(/**/){ // actually do that in the place where you showing your another viewcontroller, I don't know if you're using segues or not
destinationViewController.parentPubListViewController = self
}
}
class AnotherViewController: UIViewController {
// declare property (weak and optional to avoid crashes or memory leaks if you forget to set that property from parent view controller
weak var parentPubListViewController: PubListViewController?
// use it anywhere you need
parentPubListViewController?.navigationBarTitleImage(imageTitle: "addTitle")
}

How do I pass view as an argument (swift)

I'm trying to move my fully functioning swipe gesture code into a view model to clean up the view controller but the code uses a lot of self and view references, so I suppose I need to pass along the view or UIView.self as an argument when calling the function. Can't get it to work though. Tried:
vm.swipeCode(myView: self.view)
func swipeCode(myView: UIView) {...
But it crashes. After some research I also tried variations of inout and & but to no avail. Here's the full swipe code (it references back to the view controller but I will move those as well when things start working :) )
var myVC = RecipesViewController()
func swipeCode(myView: UIView) {
//SWIPE RIGHT
let swipingRight = UISwipeGestureRecognizer()
swipingRight.addTarget(self, action: #selector(myVC.swipeRight))
swipingRight.direction = .right
swipingRight.delegate = self as? UIGestureRecognizerDelegate
swipingRight.cancelsTouchesInView = false
myView.isUserInteractionEnabled = true
myView.addGestureRecognizer(swipingRight)
//// ALLOW SWIPE LEFT ////
let swipingLeft = UISwipeGestureRecognizer()
swipingLeft.addTarget(self, action: #selector(myVC.swipeLeft))
swipingLeft.direction = .left
swipingLeft.delegate = self as? UIGestureRecognizerDelegate
swipingLeft.cancelsTouchesInView = false
myView.isUserInteractionEnabled = true
myView.addGestureRecognizer(swipingLeft)
}
The crash is probably because of these two lines:
swipingRight.addTarget(self, action: #selector(myVC.swipeRight))
swipingLeft.addTarget(self, action: #selector(myVC.swipeLeft))
You passed myVC.swipeLeft as the action, but self as the target, so the gesture recogniser will try to find a swipeLeft method in self, which doesn't exist.
You should always ensure that the action is a member of the target:
swipingRight.addTarget(myVC, action: #selector(myVC.swipeRight))
swipingLeft.addTarget(myVC, action: #selector(myVC.swipeLeft))
You should simply make a UIView or UIViewController inherited class instead if your methods use a lot of self then simply make your controllers or views of that class.
class CustomViewController:UIViewController {
func swipeCode(myView: UIView) {
// Your code here
}
// Any other methods your code might need
}
class RecipesViewController:CustomViewController {
// Whatever else your view controller is made from
}
Or if your code swiping works in every view controller you could also extends the UIViewController to add those functionnalities

performSegueWithIdentifier not working for programmatically generated UIButtons (iOS/Swift)

Xcode 6, Swift, iOS8
I have a view controller that is populated by dynamically generated UIButtons. The number of buttons is dependent on a data feed, and is not static. I count the number of objects in the feed and generate a button for each object. Each button is supposed to segue into a details view that displays the information for its corresponding object.
In the Interface Builder I have created a segue between the two View Controllers and named it. I have not added an IBAction to initiate the segue as I cannot tie it to a specific button.
Inside the View Controller Class I execute the following:
#IBOutlet weak var localScrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
//Define the button dimensions
let buttonWidth:CGFloat = 200
let buttonHeight:CGFloat = 113
var xPos:CGFloat = 0
var scrollViewContent:CGFloat = 0
var thumbURL:String
//keeps track of the number of videos. The count is used to set a tag on the button to help identify it.
var vidCount:Int = 0
//loop through the array of recommended video objects
for index in recommended{
//Create a button for each object
var myButton = UIButton.buttonWithType(UIButtonType.System) as! UIButton
myButton.frame = CGRectMake(xPos, 0.0, buttonWidth, buttonHeight)
//For the button action I call the handleTap function detailed below
myButton.addTarget(self, action: "handleTap:", forControlEvents: .TouchUpInside)
myButton.tag = vidCount
//The image for the button is pulled from a CDN. This code sets the image in the button.
let curVal = index.thumbURL
if let url = NSURL(string: curVal) {
if let data = NSData(contentsOfURL: url){
myButton.setBackgroundImage(UIImage(data:data), forState: UIControlState.Normal)
}
}
//add the button to the scroll view
localScrollView.addSubview(myButton)
let spacer:CGFloat = 10
xPos+=buttonWidth + spacer
scrollViewContent += buttonWidth + spacer
localScrollView.contentSize = CGSize(width: scrollViewContent, height: buttonHeight)
vidCount += 1
}
}
//function to handle the tap action
func handleTap(sender:UIButton){
//Set the variable that will be passed to the next view controller
curRecVid = recommended[sender.tag]
//initiate the segue
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("toSingle", sender: self)
}
}
//prepare the data to be transferred to the next view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "toSingle") {
var targetViewController = segue.destinationViewController as! SingleViewController
targetViewController.targetVid = curRecVid
}
}
}
When I run the app in the simulator, it gets to the self.performSegueWithIdentifier("toSingle", sender: self) call and then terminates to the following uncaught exception: 'NSInvalidArgumentException', reason: '-[UILabel copyWithZone:]: unrecognized selector sent to instance 0x7fd468c8ddc0'
Any assistance in helping to track down the cause of this exception would be greatly appreciated.
It appears that the class file for the target View Controller had some sort of problem. After a comment from Epic Defeater, I wiped out its swift file, re-generated it and put in the exact same code (literally, copy/paste) and that did the trick.