How can attributes be modified from the sender in swift? For example, if I have a multiple buttons that are connected to the same event handler, how can I modify the attributes of the button (say, the title) that was pressed?
#IBOutlet weak var grade_preK: UIButton!
#IBAction func gradeButtonPressed(sender: AnyObject) {
sender.title = "New Title"
}
The handler here returns the error "Cannot assign to 'title' in 'sender'". How then, can attributes be changed on the sender of the event?
When you created this, Interface Builder may have given you the option to declare sender to be UIButton rather than AnyObject (it does have that option; you may not have noticed it). You could have chosen that, or you can fix it now:
#IBAction func gradeButtonPressed(sender: UIButton) {
And now sender is of the right type so you can modify it (and it is reasonable style to do so in Cocoa).
(Note that UIButton actually has a setTitle(_ title: String?, forState state: UIControlState) method, not a setTitle() method, so that's what you probably meant to call.)
Related
In all cases, I can wire user-interface buttons to actions with Interface Builder. But the buttons work for Objective-C but not for Swift.
Objective-C example (it works):
- (IBAction)TogglePlaying:(id)sender {
(details snipped for brevity)
}
Swift example (it doesn't work, though it's wired to its button):
#IBAction func Go(_ sender: Any) {
print("Going")
OutputText!.stringValue = InputText!.stringValue
}
I have no idea of what the difference might be, because everything I've found on using IBAction in Swift indicates that I've written it correctly. Also, in Interface Builder, I've set File's Owner's Custom Class correctly.
Update:
Using
ios - Find what Action is called by a Button in interface builder in debug mode - Stack Overflow
Find what Action is called by a Button in interface builder in debug mode
I used "Debug View Hierarchy", right-clicked on "NSButton - Go!" in the widget-hierarchy view, and selected "Print Description of NSButton - Go!"
I got
Printing description of $13:
<NSButton: 0x7fac1b116250>
I then did:
po [0x7fac1b116250 allTargets]
error: Execution was interrupted, reason: Attempted to dereference an invalid ObjC Object or send it an unrecognized selector.
The process has been returned to the state before expression evaluation.
Update:
I tried
po [0x7faf38011790 target]
(new address of that button) and I got
nil
Update:
The complete code of TLWindow, in Swift:
import Cocoa
class TLWindow: NSWindowController {
#IBOutlet weak var InputText: NSTextField!
#IBOutlet weak var OutputText: NSTextField!
override var windowNibName: NSNib.Name? {
return NSNib.Name("TLWindow")
}
#IBAction func Go(_ sender: Any?) {
print("Going")
OutputText!.stringValue = InputText!.stringValue
}
}
I don't know how to show that the xib is wired up correctly without doing a lot of screenshots. But it is, with the "Go!" button connected to "Go:" in "File's Owner". Also, "File's Owner" is set to "TLWindow", this class.
You are creating an instance of TLWindow in newDocument(), but then you're letting it go out-of-scope...
Try this:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
// add a property to "hang onto" the instance
var myTLWindow: TLWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
newDocument(self)
}
// Create an app window
#IBAction func newDocument(_ sender: Any?) {
let wc = TLWindow()
// add this line
myTLWindow = wc
wc.showWindow(self)
}
}
I'm new to swift programming, so this may be a dumb question, but how can I change the content of an UITextView Object by pressing a button.
The example I tested looks like this:
#IBOutlet weak var dataButton: UIButton!
#IBOutlet weak var dataView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func testButtonPressed(sender: UIButton) {
dataView.text = "Test worked."
}
Actually it doesn't work. My Storyboard has a ViewController from the Class where the above code is written. I also have the Button and the TextLabel. Do I need to specify anything in the Interface Builder or is something wrong with my code?
Thanks in advance for any help!!!
Make sure that your IBOutlet's and IBActions are connected in storyboard. It should work. You could set a breakpoint at the line with: dataView.text = "Test worked." to see if the code is reached. If it doesn't reach the breakpoint then your IBAction is not connected properly.
IBAction's sender parameter need an underscore before it, that is:
#IBAction func testButtonPressed(_ sender: UIButton) {
dataView.text = "Test worked."
}
are you sure you connected the code correctly?
I'm a novice and i'm practicing by making an app that takes a string through a text box and on a button click displays the string, reversed, on a label. I'm pretty sure i have the part about reversing the string correct, but i'm not exactly sure where i'm going wrong because i'm getting two errors.
one is "Argument to #IBAction method cannot have non-object type 'String'
the other "Cannot assign value to type 'ReversedCollection to type 'String?'
I'm not sure what i'm doing wrong since i haven't grasped the concept of taking a function by button action and having what is returned display on a label 100%. Help would be greatly appreciated.
Heres the code:
import UIKit
class ViewController: UIViewController {
#IBOutlet var input1 : UITextField!
#IBOutlet var label : UILabel!
#IBAction func reverse(_ input1: String){
let output = input1.characters.reversed()
label.text = String(output)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
I assume your reverse func is hooked up to the button's touch-up-inside, and you created it by Ctrl-dragging from the button to code. This would have created the correct parameter, which you then changed to have type String. When the button is touched, the parameter it sends is the button itself (eg. as sender: AnyObject).
You should change the function back to what it was originally, and then change your code to:
let output = input1.text.characters.reversed()
I'm trying to enable or disable an #IBOutlet UIButton Item of a toolbar from a UIView.
The button should get disabled when the array that I'm using in EraseView.Swift is empty.
I tried creating an instance of the view controller but it gives me the error (found nil while unwrapping):
in EraseView:
class EraseView: UIView {
...
let editViewController = EditImageViewController()
//array has item
editViewController.undoEraseButton.enabled = true //here I get the error
...
}
I tried to put a global Bool that changed the value using it in EditImageViewController but it doesn't work:
var enableUndoButton = false
class EditImageViewController: UIViewController {
#IBOutlet weak var undoEraseButton: UIBarButtonItem!
viewDidLoad() {
undoEraseButton.enabled = enableUndoButton
}
}
class EraseView: UIView {
...
//array has item
enableUndoButton = true //here I get the error
...
}
I know it's simple but I can't let it work. Here's the situation:
The root of the problem is the line that says:
let editViewController = EditImageViewController()
The EditImageViewController() says "ignore what the storyboard has already instantiated for me, but rather instantiate another view controller with no outlets hooked up and use that." Clearly, that's not what you want.
You need to provide some way for the EraseView to inform the existing view controller whether there was some change to its "is empty" state. And, ideally, you want to do this in a way that keeps these two classes loosely coupled. The EraseView should only be informing the view controller of the change of the "is empty" state, and the view controller should initiate the updating of the other subviews (i.e. the button). A view really shouldn't be updating another view's outlets.
There are two ways you might do that:
Closure:
You can give the EraseView a optional closure that it will call when it toggles from "empty" and "not empty":
var emptyStateChanged: ((Bool) -> ())?
Then it can call this when the state changes. E.g., when you delete the last item in the view, the EraseView can call that closure:
emptyStateChanged?(true)
Finally, for that to actually do anything, the view controller should supply the actual closure to enable and disable the button upon the state change:
override func viewDidLoad() {
super.viewDidLoad()
eraseView.emptyStateChanged = { [unowned self] isEmpty in
self.undoEraseButton.enabled = !isEmpty
}
}
Note, I used unowned to avoid strong reference cycle.
Delegate-protocol pattern:
So you might define a protocol to do that:
protocol EraseViewDelegate : class {
func eraseViewIsEmpty(empty: Bool)
}
Then give the EraseView a delegate property:
weak var delegate: EraseViewDelegate?
Note, that's weak to avoid strong reference cycles. (And that's also why I defined the protocol to be a class protocol, so that I could make it weak here.)
The EraseView would then call this delegate when the the view's "is empty" status changes. For example, when it becomes empty, it would inform its delegate accordingly:
delegate?.eraseViewIsEmpty(true)
Then, again, for this all to work, the view controller should (a) declare that is conforms to the protocol; (b) specify itself as the delegate of the EraseView; and (c) implement the eraseViewIsEmpty method, e.g.:
class EditImageViewController: UIViewController, EraseViewDelegate {
#IBOutlet weak var undoEraseButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
eraseView.delegate = self
}
func eraseViewIsEmpty(empty: Bool) {
undoEraseButton.enabled = !empty
}
}
Both of these patterns keep the two classes loosely coupled, but allow the EraseView to inform its view controller of some event. It also eliminates the need for any global.
There are other approaches that could solve this problem, too, (e.g. notifications, KVN, etc.) but hopefully this illustrates the basic idea. Views should inform their view controller of any key events, and the view controller should take care of the updating of the other views.
I want to connect a second button to a label #IBOutlet var ourScore: UILabel! .
The first button is connected as
#IBAction func buttonPressed(sender: AnyObject) {
ourScore.text = "\(++score)"
}
How is it possible that I can add another button to the label, so when you click on the second button it works together. The first one counts it up and the second one needs to reset it, back to 0.
Your button isn't linked to your label outlet, the code of your IBAction makes reference to it. You posted this method:
#IBAction func buttonPressed(sender: AnyObject)
{
ourScore.text = "\(++score)"
}
so create a new method:
#IBAction func resetButtonPressed(sender: AnyObject)
{
score = 0;
ourScore.text = "\(score)"
}
Link that second IBAction method to your second button. Done.
You create the button in the same exact way you created the first one but instead of ++ it'd be -- (or whatever you want it to be).
The label is available to anything inside of your class so you can manipulate it and read from it anywhere.