I'm writing some code in Xcode for an iPhone app and I want to be able to detect if a method has been run (i.e. a button was pressed, causing that method to run) in another method (I want to use an if statement so that if the button was pressed it will do this, but if it wasn't then it will do something else).
There is no has_method_been_run() function but you can check to see if the state was changed.
For example say method button_clicked() calls method change_font_to_blue(). In this case you can check to see if the font is blue and there for the method was called.
That's a very basic example of course but you could check any number of variables / the state of the UI to see if it was changed.
OR you can add a boolean to an object and just set it to true when you execute you're method that you're watching.
If your example is simply a button press I would change the UIButton's selected state
- (IBAction)buttonSelected:(UIButton *)sender {
sender.selected = YES;
// OR:
// self.myButton.selected = YES;
}
- (void)otherMethod {
if (self.myButton.isSelected) {
// button has been run through the selector method
// if you want, reset button's selected state
self.myButton.selected = NO;
} else {
// button has NOT been run through the selector method
}
}
This is a simple idea and by default the selected state of a UIButton is not visually different. If you want it to be visually changed then you can simply go into IB and change the visuals there for the selected state (including the title):
Then when the button is set to selected (self.myButton.selected = YES;) it will automatically change what the button looks like!
Related
I have a UIView with two buttons on it. In the MyView class I have this code:
-(BOOL) canBecomeFocused {
return YES;
}
-(NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments {
return #[_editButton, _addButton];
}
-(IBAction) editTapped:(id) sender {
BOOL editing = !tableViewController.editing;
[_editButton setTitle:editing ? #"Done" : #"Edit" forState:UIControlStateNormal];
_addButton.hidden = !editing;
[tableViewController setEditing:editing animated:YES];
}
The basic idea is that the user can move the focus to the edit button, which can then make the Add button appear.
The problem started because every time I tapped the edit button, focus would shift to the table view. I would actually like it to move to the Add button. I also want it so that when editing it deactivated, the edit button keeps the focus. but again it's shifting down to the table view.
So I tried the above code. This works in that focus can move to the view and on to the button. But once it's there, I cannot get it to move anywhere else.
Everything I've read says just override preferredFocusEnvironments but so far I've not been able to get this to work. Focus keeps going to a button then refusing to move anywhere else.
Any ideas?
If anybody is facing this issue, Just check if you are getting the following debug message printed in the console.
WARNING: Calling updateFocusIfNeeded while a focus update is in progress. This call will be ignored.
I had the following code :
// MARK: - Focus Environment
var viewToBeFocused: UIView?
func updateFocus() {
setNeedsFocusUpdate()
updateFocusIfNeeded()
}
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if let viewToBeFocused = self.viewToBeFocused {
self.viewToBeFocused = nil
return [viewToBeFocused]
}
return super.preferredFocusEnvironments
}
I was calling the updateFocus() method multiple times while viewToBeFocused was either nil or some other view. Debugging the focus issues mainly between transition is really difficult. You should have patience.
Important to note: This depends on your use case, but if you want to
update the focus right after a viewcontroller transition (backward
navigation), You might have to set the following in viewDidLoad:
restoresFocusAfterTransition = false // default is true
If this is true, the view controller will have the tendancy to focus the last focused view even if we force the focus update by calling updateFocusIfNeeded(). In this case , since a focus update is already in process, you will get the warning as mentioned before at the top of this answer.
Debug focus issue
Use the following link to debug the focus issues: https://developer.apple.com/documentation/uikit/focus_interactions/debugging_focus_issues_in_your_app
Enable the focus debugger first under Edit scheme > Arguments passed on launch:
-UIFocusLoggingEnabled YES
This will log all the attempts made by the focus engine to update the focus. This is really helpful.
You can override the preferredFocusEnviromnets with the following logic:
-(NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments {
if (condition) {
return #[_editButton];
}
else {
return #[_addButton];
}
}
After setting it, you can call
[_myView setNeedsFocusUpdate];
[_myView updateFocusIfNeeded];
The condition could be BOOL condition = tableViewController.editing; or sg like that.
If that now works, you can call it with a delay (0.1 sec or so).
I have a custom UIControl subclass with an action method callback. I want to display the value of the control element on a UILabel while it is being adjusted, and then I want the label to become hidden when the user stops adjusting the control.
Therefore, I have connected the action for both UIControlEventValueChanged and UIControlEventTouchUpInside. Both successfully invoke my action method. However, to do different things in this method based on the action I need to know which event triggered the method. How can I do this? I've looked through UIControl and don't see an obvious property. state seems to return 1 for both actions.
So something like this:
- (void)handleSlider1:(CustomSlider*)sender {
if (sender.state == UIControlEventValueChanged) {
// code
} else {
// different code
}
}
You can distinguish the two events pretty easily by connecting them each to separate IBActions. Each new action then would call your single handler, passing the appropriate UIControlEvent value along:
- (IBAction)sliderValueChanged:(CustomSlider *)slider
{
[self handleSlider1:slider forEvent:UIControlEventValueChanged];
}
- (IBAction)sliderValueChanged:(CustomSlider *)slider
{
[self handleSlider1:slider forEvent:UIControlEventTouchUpInside];
}
- (void)handleSlider1:(CustomSlider *)sender forEvent:(UIControlEvents)event
{
if (event == UIControlEventValueChanged)
//...
}
If you want to use the same method to handle both events then you can check the highlighted property of the control:
if (sender.highlighted) {
// slider is changing value (value changed)
} else {
// slider has stopped changing value (touch up inside)
}
Alternatively, you could simply create two separate action methods and connect each event to the required method.
I have an app with some buttons, when those buttons are pressed the image on them should change. I assume that the TouchUpInside runs when you tap and remove the finger while still holding inside the area of the element, however it only works rarely and I'm not sure why.
The reason I use TouchUpInside instead of TouchDown is because I want the user to be able to cancel the action.
I'm sorry if I've misunderstood anything about those events and if this has already been asked. I couldn't find an answer to my problem searching the web.
//The IBAction is set to trigger on TouchUpInside
#IBAction func action11(sender: UIButton) {
setTile(sender)
}
func setTile(sender: UIButton) {
if turn {
print("O's turn")
sender.setImage(xTile, forState: .Normal)
turn = false
}
}
EDIT: Added the necessary code
There are some properties of UIButtons which you can use to achieve what you want.
You can use Default and selected state of uibutton to set two different images.
In XIB select state "Default" and assign default image to that state again select state to "Selected" and assign image which you want after button section.
and add following line in button selection method.
-(IBAction)buttonTapped:(UIButton *)sender{
sender.selected = !sender.selected;
}
Your understanding is correct, you need to use touchUpInside.
I assume you are trying to create a button that has a toggle function. On one touch you want the button to have the value Say "X" and when touched again the button has a value "O".
Take a look at this code below, this should do the job.
class ViewController: UIViewController {
var isButtonPressed = false{
// Adding a Property Observer, that reacts to changes in button state
didSet{
if isButtonPressed{
// Set the Value to X.
}else{
// Set the Value to O.
}
}
}
#IBAction func changeButtonValue(sender: UIButton) {
// Toggle the button value.
isButtonPressed = !isButtonPressed
}
}
If you don't set turn=true after the first time, this code is executed it will be executed only one.
if turn {
print("O's turn")
sender.setImage(xTile, forState: .Normal)
turn = false
}
Check if the button frame is large enough to get finger touch.
Apple says at least 35x35 pixel.
I'm learning OBJ.C/Xcode/Cocoa here and have run into a question I can't seem to find an answer for.
I want the text in a UILabel to switch between two states (ON/OFF for example) each time the user clicks the same button.
I can get the label to switch to one state, but can't get it to switch "back" when the user clicks the button again. I am assuming there is some logic required, or checking the status of the object once the button is clicked...
Does this require me to keep track of a bool or the "state" of the Label (or button)?
Would this require two methods to be associated to the button, or can it happen with just one?
Thanks for any guidance/pointing in the right direction/Code snips!!!!
~Steve
I got an answer, and it works:
- (IBAction)flip:(id)sender
{
_flipButton.selected = !_flipButton.selected;
self.flipLabel.text = (_flipButton.selected) ? #"ON" : #"OFF";
}
- (IBAction)flip:(id)sender {
[yourButton setSelected:!yourButton.selected];
self.flipLabel.text = (yourButton.selected) ? #"ON" : #"OFF";
}
This toggles the selected state of the button then checks its state to determine which string to pass to the text property of your label.
the easiest way (I think) is to store a BOOL with the state of the UILabel and (on each click of the button) negate the BOOL and set the appropriate text of the Label
You could call this: yourLabel.enabled = !yourLabel.enabled on button click to change the enabled-state of your UILabel. Or what kind of state do you mean ?
Just noticed something peculiar with my app. Whenever I tap one of the bar buttons to open a popover viewcontroller, if I tap it again it simply opens another instance of that vc (I can keep doing this).
How do I stop this? Should I use a boolean to disable the button when the boolean is active and then somehow reset it when the user closes the VC by other means (such as tapping part of the screen that isn't the same VC)?
Tried the boolean suggestion:
In my prepareForSegue method I have the following:
if(isActive==false){
InformationViewController *informationViewController = [segue destinationViewController];
informationViewController.delegate = self;
isActive = true;
}
This may no longer be important, but I would recommend the boolean solution you proposed with one modification. If you move the
isActive = true;
statement to viewWillAppear, I'm pretty sure the button will remain disabled until the modal view closes.
Yes, I would suggest using a global bool value. Set the variable to true when the button is pressed.
In the function that creates the instance, check to make sure that the variable is false before creating the instance.
Once the instance is deleted, set the variable back to false.
Psuedo-code (in C++):
bool isActive = false;
void CreateInstance()
{
if (isActive == false)
{
-- code
isActive = true;
}
}
void InstanceDestroyed()
{
-- code
isActive = false;
}
You could set the button to disable once the view appears, and then add code in your popover view to:
a) send a notification using Notification Center once the popover is dismissed to be "caught" by the view that holds the button and re-enable the button, or...
b) use the delegation pattern using a protocol to handle the re-enabling of the button once the popover view is dismissed.
These methods might require a little bit more work, but I try not to use any global variables in a MVC pattern.