Working on a tutorial with a NSSlider.
What I want: Moving the slider will show slider value in NSTextField.
Tutorial explains that following method will show slider value in text field:
- (IBAction)sliderDidMove:(id)sender {
NSSlider *slider = sender;
double value = [slider doubleValue];
[sliderValueLabel setDoubleValue:value];
}
Method does not work so I tried to find the method declaration on Apples developer website
but couldn't find it. From my understanding is the method: sliderDidMove a class method
from the class NSSlider so why I'm unable to find any documentation?
When slider value changes, it sends -[NSControl action] to its -[NSControl target]. So in Interface Builder you need to Ctrl-drag from the slider to the object that has sliderDidMove: (that will probably be either App Delegate or File's Owner). The name is chosen by the tutorial's author, it can be anything else.
Alternatively, you can set it up programmatically:
[slider setTarget:self]; // assume the handler is [self sliderDidMove:]
[slider setAction:#selector(sliderDidMove:)];
Note also that this particular task is better solved with bindings: bind both slider's and label's value to a double property of some object, and Cocoa will keep them synchronized.
In Swift ...
// Somewhere maybe in viewDidLoad ...
slider.target = self
slider.action = #selector(sliderDidMove)
// Later on..
func sliderDidMove(){
print("The slider moved!")
}
Related
This seems like it should be pretty straight-forward but I'm having trouble finding a working example, good documentation, or even many StackOverflow posts that are helpful.
I have a custom view which contains an AVPlayer, like so:
#implementation
{
#private
AVPlayerViewController *controller
}
- (id) init
{
self = [super init];
if (self)
{
controller = [[AVPlayerViewController alloc] init];
controller.view.frame = self.view.bounds;
[self.view addSubview:controller.view];
}
return self;
}
#end
(I have a few other views like a message that overlays the player, a poster that I display while swapping videos, etc - but this is the basic setup)
When I integrated the IMA SDK, I started to have issues. If you press the pause button on the remote control during an ad, it pauses the ad just fine. But if you press the pause button again it doesn't unpause the ad, but instead unpauses my content player behind the ad. I don't hear any audio, but I know the content player was unpaused because I have ID3 metadata in my video and an NSLog() statement when I hit it, and I begin to see these logs. If I press the pause button again, the logs pause. I press it a fourth time, the logs start up again.
To try and fix this I wanted to bind a listener to the remote's play/pause button and make sure that if I was playing an ad then the ad was resumed, not the content. So I tried adding the following to my init method on my view:
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self.view action:#selector(tapped:)];
[tapRecognizer setAllowedPressTypes:#[ [NSNumber numberWithInt:UIPressTypePlayPause] ]];
[self.view addGestureRecognizer:tapRecognizer];
I then created the following method:
- (void) tapped: (UITapGestureRecognizer *) sender
{
NSLog(#"Tapped");
}
This isn't being called. I'm pretty confident I made a simple mistake, but the documentation isn't very clear, so I'm not sure what I should be doing instead. The official documentation on detecting button presses uses Swift and says:
let tapRecognizer = UITapGestureRecognizer(target: self, action: "tapped:")
tapRecognizer.allowedPressTypes = [NSNumber(integer: UIPressType.PlayPause.rawValue)];
self.view.addGestureRecognizer(tapRecognizer)
I believe I translated those three lines well. The documentation then doesn't show what the tapped method should look like, but instead goes on a tangent about working with low-level event handling. So to get the appropriate method signature I looked at the documentation on UITagGestureRecognizer which had the following (Swift) example for writing a handler:
func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .ended {
// handling code
}
}
This is why I went with - (void) tapped: (UITapGestureRecognizer *) sender
Still, after all of that, it's not working.
Quick Update
I tried replacing:
initWithTarget:self.view
With:
initWithTarget:controller.view
And:
self.view addGestureRecognizer
With:
controller.view addGestureRecognizer
And this time it looks like something actually happened when I pressed the play/pause button. The app crashed and Xcode gave me the following error:
2019-12-17 12:16:50.937007-0500 Example tvOS App[381:48776] -[_AVPlayerViewControllerContainerView tapped:]: unrecognized selector sent to instance 0x10194e060
So it seems like (correct me if I'm wrong):
The AVPlayerViewController has the focus, not my view
The gesture recognizer calls the selector on whatever class you register it to, rather than the class that did the registering
So I guess an alternative question to my original would be: How do I allow my class to handle a gesture on some other class (e.g. AVPlayerViewController)?
The target does not need to equal the view that the gesture recognizer is attached to. You can set the target to MyView but still attach the gesture recognizer to controller.view
To work around the unrecognized selector crash, you need to make sure you're providing the correct object as the target for your gesture recognizer. The way UIGestureRecognizer works is that when the gesture is recognized, it will invoke the given selector on the given target object. In other words, when gesture fires, it's going to perform the equivalent of:
[target selector:self];
(You seem to be treating the target as the view that the gesture will be attached to, which isn't how it works)
So if you implemented tapped: on your class MyView, then the target you pass to the gesture recognizer initializer should be an instance of MyView. You probably want to provide self, not self.view.
I have a textfield and a button. When I click inside the textfield, I want the button to disappear. I defined the textfield as both outlet and action ( with event “Did end on exit”). In the method for the textfield, I have self.testButton.hidden = YES; When I click inside the textfield, the button does not go away. Instead, it remains until I hit the return key on the keyboard – causing the keyboard to go away. I tried the same thing w/ touchup inside as the event on the text field. When you click in the text field, nothing happens to the button.
Instead of using the Target-Action mechanism ("Did end on exit" and "Touch Up Inside") use the Delegate mechanism.
First, make your class conform to the UITextFieldDelegate protocol. In your *.h (header) file add the following:
// Here I'm assuming your class is inheriting from UIViewcontroller but it
// may be inheriting from some other class. The really important part here
// is: <UITextFieldDelegate>. That's how you make your class conform to that protocol
#interface THE_NAME_OF_YOUR_CLASS : UIViewController <UITextFieldDelegate>
.
Second, implement the -(void)textFieldDidBeginEditing:(UITextField *)textField method. Also, remember to set yourself as the delegate too: self.textField.delegate = self. That way, the method will get called every time the user starts editing. Inside that methdod call self.testButton.hidden = YES;. In your *.m (implementation) file add the following:
-(void)viewDidLoad {
// here I'm assuming you have a 'strong' reference to your text field.
// You're going to need one to set yourself as the delegate.
self.textField.delegate = self;
}
// This is one of the methods defined in the UITextFieldDelegate protocol
-(void)textFieldDidBeginEditing:(UITextField *)textField {
self.testButton.hidden = YES;
}
.
Similarly, to make your button appear again, implement the - (void)textFieldDidEndEditing:(UITextField *)textField method. Inside it un-hide your button. Again, in your *.m file add the following:
// This is another method defined in the UITextFieldDelegate protocol
-(void)textFieldDidEndEditing:(UITextField *)textField {
self.testButton.hidden = NO;
}
Although delegates may be a mystery to you right now once you become familiar with them
you will realize they're very easy. And this is very important because iOS programming
relies heavily on delegates.
A delegate is a "notification" mechanism based on the "Hollywood" principle which is: don't call us; we'll call you.
In your case the class that contains the UITextField is interested in knowing when the UITextField begins editing and when it ends editing. But your class cannot be "polling" (that is, constantly asking) the text field to find out if the state changed. Instead you register your class with the text field and it will be the text field the one that will let you know when something happened. That will be thanks to the methods that you implemented.
Further reading: protocols and delegates
Hope this helps!
Have you made sure that testButton has its IBOutlet set before you hide it?
If you want to button to disappear when the user begins editing the text field, try UIControlEventEditingDidBegin.
I have two buttons in single-view app main storyboard and would like to disable one of them as well as get value from the UILabel using awakeFromNib method. As far as I concern all relationships and GUI items must be initialized and values must be assigned before calling the awake method. Unfortunately I am not able to do get the value and disable button by applying
- (void)awakeFromNib {
decreaseButton.enabled = NO;
decreaseButton.alpha = 0.2;
[polygon initWithNumberOfSides:numberOfSidesLabel.text.integerValue
minimumNumberOfSides:3
maximumNumberOfSides:12];
}
to the class I have made. I have established the connection between the UILabel and
IBOutlet UILabel *numberOfSidesLabel;
in my created class file.
Can somebody see the mistake or shall I provide more info on the problem?
First, you must call [super awakeFromNib]; when you override this method.
Second, standard practice is to use viewDidLoad. Try that instead.
I currently have a custom view class which is drawing a 2d game board representation on a window.
When the user clicks on the board I am using the mouseDown: event to calculate the cell co-ordinates from the mouse position (I am doing this within the custom view class).
- (void)mouseDown:(NSEvent*)theEvent {
// Get position of mouse click:
NSPoint point = [theEvent locationInWindow];
NSPoint mousePos = [self convertPoint:point fromView:nil];
// Calculate which cell has been clicked:
int cellX = mousePos.x / gridSize.width;
int cellY = mousePos.y / gridSize.height;
}
(In the above snippet 'gridSize' is an NSPoint instance variable that I am using to store the height and with of each cell in the game board)
Is it possible for me to create an IBAction style message in my controller class that can be used to send this information?
The way I was imagining this could work was:
Declare instance variables for clicked co-ordinates in the custom view.
Implement an (IBAction) in the controller class:
Use the (id)sender pointer to access the instance variables that hold the co-ordinates
Link the IBAction to the custom view in interface builder
To try this I declared 'cellX' and 'cellY' as instance variables in my custom view. I then implemented the following in my controller class:
-(IBAction)cellClicked:(id)sender {
[self setCellAtPosX:[sender cellX] PosY:[sender cellY];
}
(cellX and cellY are accessor methods to the instance variables)
As I was expecting it is not that simple, and it will not let me link the IBAction to the custom view. Is there anything missing from my approach to implementing this?
I can imagine that all this would need is some kind of notifier that tells interface builder where to send out a target-action message - in my case, at the end of the mouseDown: implementation, but I cannot find where to begin with this!
You may have less trouble implementing this with a delegate pattern, i.e. set a IBOutlet delegate property on your board view and have the controller implement this protocol. In Interface Builder you will link your view and controller via the outlet, and the view will call it's delegate method in the mouseDown implementation, for instance:
- (void)mouseDown:(NSEvent*)theEvent {
...
[self.delegate boardView:self didSelectCellAtX:x Y:y];
}
I'm trying to understand how to catch a "text changed" event from a text field in my window. I'm used to Java's "action listeners", and can't find anything similar in Objective-C/Cocoa.
I searched for quite a while and found the "key value observing" protocol, but the observeValueForKeyPath: method (function?) only triggers when the value of my text field was changed in code (using [textfield setStringValue:...], e.g.), not by typing in it.
How can I "listen" to the value change when a user types in the text field?
You can set a delegate for your NSTextField instance and have the delegate implement the following method:
- (void)controlTextDidChange:(NSNotification *)notification {
// there was a text change in some control
}
Your delegate object can be the application delegate, a window controller, a view controller, or some other object in your application. The delegate can be programatically set via
[myTextField setDelegate:delegateObject];
or, in Interface Builder, via the delegate outlet available in the NSTextField control.
Note that if there are multiple controls hooked to the same delegate then -controlTextDidChange: will be sent for each control, i.e., the same method is called for different controls. If you want different behaviour according to the control where the text has changed, you can use -[NSNotification object] to identify the control that has sent the notification.
For instance, if you have two text fields with corresponding outlets nameField and addressField, and you’ve set the same delegate for both fields, then:
- (void)controlTextDidChange:(NSNotification *)notification {
// there was a text change in some control
// [notification object] points to the control that has sent
// the notification
if ([notification object] == nameField) {
// nameField has changed
}
else if ([notification object] == addressField) {
// addressField has changed
}
}
Alternatively, you could have one delegate for each text field. In this case, there’d be no need to test [notification object].
You can also just hook up to the "Editing Changed" from IB and create the Action to handle it
- (IBAction)txtField_Changed:(id)sender
{
// my textfield has been changed
}
This works for me
func textView(textView: NSTextView, shouldChangeTextInRange affectedCharRange: NSRange, replacementString: String?) -> Bool {
print("Changed!")
return true
}
You can use textFieldShouldBeginEditing: method of UITextFieldDelegate. In iOS listeners are called NSNotifications
EDIT
In objective-c a lot of UIObjects have a corresponding protocol class that's called "delegate" The delegate is responsible for reacting to events. So to be able to respond or to be notified about actions you need to implement the delegate and its methods.