I am creating a UIButton in another file opposed to the main ViewController. I created this button
var newNoteButton = UIButton(frame: CGRectMake(5, 18, 152.5, 37))
newNoteButton.backgroundColor = UIColor.grayColor()
newNoteButton.addTarget(self, action: ("newNoteButtonAction:"), forControlEvents: UIControlEvents.TouchUpInside)
newNoteButton.setTitle("New Note", forState: UIControlState.Normal)
newNoteButton.userInteractionEnabled = true
self.view.addSubview(newNoteButton)
with action
func newNoteButtonAction (sender: UIButton!){
println("New Note")
}
It is throwing the above error even though, if i copy and paste the same code into my ViewController, it doesn't flag me at all. Why is it doing this? It is meant to just print out the string "New Note" but something causes Thread 1 to queue.
Edit:
After a bit of reading, I have been able to reduce the amount of code to just this:
var newNoteButton = UIButton(frame: CGRectMake(5, 18, 152.5, 37))
newNoteButton.backgroundColor = UIColor.grayColor()
newNoteButton.addTarget(self, action: ("newNoteButtonAction:"), forControlEvents: UIControlEvents.TouchUpInside)
and this:
func newNoteButtonAction (sender: UIButton!){
println("New Note")
}
I tried removing the action and the problem was still there. This exists within my textView class in a separate file. In my AppDelegate file, the Root View Controller is ViewController. If I move the button to ViewController, the issue does not present itself. The only code in my ViewController is this
import Foundation
import UIKit
class ViewController: UIViewController, UITextViewDelegate, UIScrollViewDelegate {
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
//Global Variables
var scrollView = UIScrollView()
override func viewDidLoad(){
super.viewDidLoad()
//Add textView
//let textViewRef = textView() Removed to test
//self.view.addSubview(textViewRef.view)
//Button Code
var newNoteButton = UIButton(frame: CGRectMake(5, 18, 152.5, 37))
newNoteButton.backgroundColor = UIColor.grayColor()
newNoteButton.addTarget(self, action: ("newNoteButtonAction:"), forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(newNoteButton)
}
func newNoteButtonAction (sender: UIButton!){
println("New Note")
}
}
I have tried messing about with conditionals and the semicolon in the action but that doesn't seem to affect it or help in any way. If you need any more information, please feel free to ask.
Edit 2
My other View Controller looks like this:
import Foundation
import UIKit
class textView: UIViewController {
//Globals
var newNoteButton = UIButton()
override func viewDidLoad()
newNoteButton = UIButton(frame: CGRectMake(5, 18, 152.5, 37))
newNoteButton.backgroundColor = UIColor.grayColor()
newNoteButton.addTarget(self, action: ("newNoteButtonAction:"), forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(newNoteButton)
}
func newNoteButtonAction (sender: UIButton!){
println("New Note")
}
}
Edit 3: I just noticed that this information might be a bit relevant. The error is on the class AppDelegate line in my delegate file.
I had the same problem and I received the same error that you. I wanted create a button in other class. I recommend you read my solution: How invoke a method in the method addTarget of the Button class when I create programmatically the button in a plain class in Swift . An abstract, you should inherit of the "UIClass" that you require. If you want to create a button, you should inherit of "UIButton" class. Then, you set the values of the attributes of this class instead of create a button.
FYI for anybody with this issue in the future, it can usually be debugged back to the retention of the button within the class you are adding the target to.
Try moving the declaration of the button or targeted class up to a class global to be cleaned up after the view is disposed of.
Example: In the instance listed above, declare the newNoteButton at the class level instead of inside the viewDidLoad function to ensure it's retained while it can call it's target.
class ViewController: UIViewController, UITextViewDelegate, UIScrollViewDelegate {
// Global Variables
var scrollView = UIScrollView()
var newNoteButton: UIButton! // <- Declare button here instead of in viewDidLoad
}
Related
Is there any way to use addTarget on something other than self (which seems to be the most common use case)?
Yes, you can use a target other than self. The most common use is to call addTarget with self where self is a reference to the viewController that the adds the UIControl to its view hierarchy. But you aren't required to use it that way. The target is merely a reference to an object, so you can pass it a reference to any object you want. The action is a Selector which needs to be defined as an instance method on the class of that object, and that method must be available to Objective-C (marked with #objc or #IBAction) and it must take either no parameters, just the sender, or the sender and the event.
You can also pass nil as the target, which tells iOS to search up the responder chain for the action method.
Here's a little standalone example:
import UIKit
class Foo {
#objc func buttonClicked() {
print("clicked")
}
}
class ViewController: UIViewController {
let foo = Foo()
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 50, y: 200, width: 100, height: 30))
button.setTitle("Press me", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.addTarget(foo, action: #selector(Foo.buttonClicked), for: .touchUpInside)
self.view.addSubview(button)
}
}
You can certainly set up some other object to receive control actions. Consider the following view controller:
First, define a class who's job is to respond to button tap actions:
#objc class ButtonTarget: NSObject {
#IBAction func buttonAction(_ sender: Any) {
print("In \(#function)")
}
}
Now define a view controller that creates a ButtonTarget object
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
lazy var buttonTarget = ButtonTarget() //Give the ViewController a `ButtonTarget`
override func viewDidLoad() {
super.viewDidLoad()
//Add a taret/action to the button that invokes the method `buttonAction(_:)`
button.addTarget(
buttonTarget,
action: #selector(ButtonTarget.buttonAction(_:)),
for: .touchUpInside)
}
}
I did some research but I could not find an appropriate answer regarding the following question.
I don't use the interface builder to generate my views so every control is defined programmatically like so :
let loginButton: UIButton = {
let button = UIBUtton()
button.setTitle("Title", forState: .Normal)
// adding some properties to my button
return button
}()
Then I add it to the view of my ViewController like that :
self.view.addSubview(loginButton)
And I add some constraints with the Visual Format Language
For example :
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat
_("V:|[v0(25)]|", options:NSLayoutFormatOptions(), metrics: nil,
_views: ["v0":loginButton]))
It works really well and I prefer it that way(over the IB) but the main issue is that I end up with really big ViewController classes as I define every controls in the ViewControllers classes.
I guess I should have all the controls declarations, constraints,... in a separate file but I can't find how to do that.
I tried to create a separate class, for example LoginView, which contains all the controls, and a function setupViews(superView: UIView) that adds subviews to the superView parameter and specify constraints. Then in the viewDidLoad() method of my ViewController, I instantiate this class (LoginView) and execute the setupView(superView: self.view). It looks like this :
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let loginView = LoginView()
loginView.setupViews(self.view)
}
}
I am pretty sure this is not the way to go. It also causes problems when I try to addTarget to some of my controls.
My question, what is the best way to do it, following the MVC pattern.
Thank you very much !
EDIT 1 :
As asked in the comments, here is the implementation of the LoginView class (I only kept few controls/constraints to keep it short but it is the same process for every other one)
import UIKit
class LoginView {
let superView: UIView?
init(tempSuperView: UIView){
superView = tempSuperView
}
let usernameTextField: UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.textColor = UIColor.whiteColor()
return textField
}()
let loginButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Login", forState: .Normal)
return button
}()
//some more controls definition
func setupViews() -> Void {
superView!.addSubview(usernameTextField)
superView!.addSubview(loginButton)
superView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat
_("V:|-150-[v0(50)]", options:NSLayoutFormatOptions(), metrics: nil,
_views: ["v0":usernameTextField]))
superView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat
_("H:|-150-[v0(25)]", options:NSLayoutFormatOptions(), metrics: nil,
_views: ["v0":usernameTextField]))
//same for the button
}
}
Here is my ViewController :
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let loginView = LoginView(tempSuperView: self.view)
loginView.setupViews()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In the creation of a swift iOS app, I needed to handle the event of a UIButton press outside of the parent view controller, so I created a (very simple) protocol to delegate that responsibility to a different class:
import UIKit
protocol MyButtonProtocol {
func buttonPressed(sender: UIButton)
}
However, when I try to addTarget to a UIButton with that protocol, I get this error: Cannot convert value of type 'MyButtonProtocol' to expected argument type 'AnyObject?'. Shouldn't anything be able to be converted to AnyObject?? Here is my main code:
import UIKit
class MyView: UIView {
var delegate: MyButtonProtocol
var button: UIButton
init(delegate: MyButtonProtocol) {
self.delegate = delegate
button = UIButton()
//... formatting ...
super.init(frame: CGRect())
button.addTarget(delegate, action: "buttonPressed:", forControlEvents: .TouchUpInside)
addSubview(button)
//... more formatting ...
}
}
Thanks in advance.
AnyObject is the protocol to which all classes conform.
To define a protocol which can only adopted by classes, add
: class to the definition:
protocol MyButtonProtocol : class {
func buttonPressed(sender: UIButton)
}
Without that modification,
var delegate: MyButtonProtocol
can be a struct or enum type and that is not convertible to AnyObject.
//i hope it will work
import UIKit
class MyView: UIView {
var delegate: MyButtonProtocol
var button: UIButton
init(delegate: MyButtonProtocol) {
self.delegate = delegate
button = UIButton()
//... formatting ...
super.init(frame: CGRect())
button.addTarget(delegate, action: Selector("buttonPressed:") forControlEvents: .TouchUpInside)
addSubview(button)
//... more formatting ...
}
}
I have a baseVc that all my UIViewController inherit from, all of which will have the same buttons in the UINavigationBar, if they are embedded in a UINavigationController.
What I am trying to do is set the target and action of one of these buttons from the child class, however I am having no luck. How can I get this to work?
#interface BaseVc : UIViewController
#property (strong,nonatomic) UIBarButtonItem *actionButton;
#end
Above is the header of my parent class, and in the child class:
[[super actionButton] setTarget:self];
[[super actionButton] setAction:#selector(viewMenu)];
If I've understand correctly, you should do next: assign action to the button inside the parent class, -doRightMenuButtonAction for example, and then just override this method in the child class and handle this action.
As your subclass inherits from BaseVC, it can access actionButton property. So Jjust try:
[self.actionButton setTarget:self];
[self.actionButton setAction:#selector(viewMenu)];
Make sure you are not marking your buttons as private.
class BaseViewController: UIViewController {
var button = UIButton.buttonWithType(UIButtonType.System) as UIButton
override func viewDidLoad() {
super.viewDidLoad()
button.frame = CGRectMake(20, 200, 200, 44)
button.setTitle("Hello World", forState: .Normal)
self.view.addSubview(button)
}
}
and then in your subclass...
class AceViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: "sayHello:", forControlEvents: .TouchUpInside)
}
func sayHello (button: UIButton) {
UIAlertView(title: "Hello", message: "World", delegate: nil, cancelButtonTitle: "Ok").show()
}
}
class Test: NSObject {
init(mainView:UIView){
super.init()
var button = UIButton.buttonWithType(UIButtonType.System) as! UIButton
button.setTitle("Test", forState: UIControlState.Normal)
button.backgroundColor = UIColor.whiteColor()
button.frame = CGRectMake(0, 0, 250, 40)
button.addTarget(self, action: "doSomething", forControlEvents: UIControlEvents.TouchUpInside)
mainView.addSubview(button)
}
func doSomething(){
println("test")
}
}
in other class I try to doSomething() by clicking this button, but unsuccessfully my app crash...
var testvar = Test(view)
How to trigger this function by button pressed without app crash ?
It's a memory management issue. Your problem is that the Test instance you stored in the local variable testvar will be released after you left the method. The addTarget of UIButton method does not retain it's target, so there are no more strong references to the Test instance and it will be deallocated. If you tap the button the system tries to call doSomething on the now deallocated instance. Which leads to a crash.
You could store the instance in a instance variable of your UIViewController (or whatever object calls var testvar =).
e.g.:
class FirstViewController: UIViewController {
var testvar: Test!
override func viewDidLoad() {
super.viewDidLoad()
testvar = Test(mainView:self.view)
}
}