How to update one UISwitch when using another UISwitch - ios

Case: I have 2 UISwitches - Switch1 and Switch2.
Switch1 controls the visbility of a UILabel in my scene.
Switch2 once activated turns Switch1 off as well as its visbility.
Problem: After Switch2 is activated Switch1 turns off as well as disappears however my UILabel is still visible in my scene.
switch1.addTarget(self, action: #selector(switch1Action), for: UIControl.Event.valueChanged)
switch2.addTarget(self, action: #selector(switch2Action), for: UIControl.Event.valueChanged)
#objc func switch1Action(switch1: UISwitch) {
if switch1.isOn {
lockedMessage.isHidden = false
}
if !switch1.isOn {
lockedMessage.isHidden = true
}
}
#objc func switch2Action(switch2: UISwitch) {
if switch2.isOn {
switch1.isOn = false
switch1.isHidden = true
}
Many thanks! :)

If I understand your issue correctly, then it seems you want lockedMessage to be hidden if switch2 is on as well. If this is the case – you can change the visibility of the lockedMessage within your function switch2Action.
#objc func switch2Action(switch2: UISwitch) {
if switch2.isOn {
switch1.isOn = false
switch1.isHidden = true
lockedMessage.isHidden = true
}

This is correct and desired behavior. Since you explicitly changed the value, it is up to you to decide how to handle the changed value.
The reason for this is because it is not uncommon to explicitly change the value of control after being notified of its value being changed through user interaction. If the explicit state change caused the event to fire again, you would end up in an infinite loop. "#rmaddy"
Deprecated (since the original question updated):
You (probably accidentally) set a unrelated condition for hiding the label. (What is moveWall ?).
Try this instead:
#objc func switch1Action(switch1: UISwitch) {
lockedMessage.isHidden = !switch1.isOn
,,,
}

Setting switch1.isOn programmatically will not trigger the switch1Action. You need to hide the message label from switch2Action.
For reference see the documentation:
Setting the switch to either position does not result in an action message being sent.

Related

Can't change UIView.isHidden property after action is called by UIBarButton

I am using UIView's isHidden property to show/hide controls on a posting interface I'm developing. I use this to change the controls available when the keyboard is up/down, post types are selected, etc. I have written a method triggered by notifications that sets the view properties (mainly .isHidden) and animates changes, which is called when the keyboard appears, or when the user selects a post type which then changes the controls available to them. This usually works perfectly fine, and the properties change as intended, but when the method is triggered by a UIBarButton action, the view being set to visible becomes unresponsive. After hitting bar button I am unable to change the isHidden property of the view (even by explicitly setting isHidden to true or false... it does not change).
Here is the method that changes the view properties:
#objc func animateActionViewChange(_ sender: Notification) {
// DEBUG
print("\n[BEFORE]\nnormalActionView.isHidden: \(normalActionView.isHidden)\nkeyboardActionView.isHidden: \(keyboardActionView.isHidden)\nkeyboardUp: \(keyboardUp)\nactionViewActive: \(actionViewActive)")
// save prev state
let normalTemp: Bool = normalActionView.isHidden
let keyboardTemp: Bool = keyboardActionView.isHidden
// set new state based on env vars
normalActionView.isHidden = keyboardUp ? true : !actionViewActive
keyboardActionView.isHidden = keyboardUp ? !actionViewActive : true
// DEBUG
print("[AFTER]\nnormalActionView.isHidden: \(normalActionView.isHidden)\nkeyboardActionView.isHidden: \(keyboardActionView.isHidden)\nkeyboardUp: \(keyboardUp)\nactionViewActive: \(actionViewActive)")
// animate opacity changes
if normalActionView.isHidden != normalTemp {
let targetAlpha: CGFloat = normalTemp ? CGFloat(1) : CGFloat(0)
UIView.animate(withDuration: actionViewAnimationDuration / 2) {
self.normalActionView.alpha = targetAlpha
}
}
if keyboardActionView.isHidden != keyboardTemp {
let targetAlpha: CGFloat = keyboardTemp ? CGFloat(1) : CGFloat(0)
UIView.animate(withDuration: actionViewAnimationDuration / 2) {
self.keyboardActionView.alpha = targetAlpha
}
}
}
and the action called by the UIBarButton (this issue concerns when the button title is 'back'):
// back/cancel button action
#IBAction func cancel(sender: UIBarButtonItem) {
if sender.title == "Cancel" {
// cancel ongoing request if there is one
if let request = ongoingRequest {
if !request.isFinished {
request.cancel()
}
}
self.performSegue(withIdentifier: "cancel_unwindFromNewPostView", sender: self)
} else {
// reset post type to default (general)
postType = .general
// set actionViewActive bool to set visibility going forwards
actionViewActive = true
// hide datePicker
self.datePickerView.isHidden = true
actionViewAnimationDuration = 0.35
NotificationCenter.default.post(name: NSNotification.Name("animateActionViewChange"), object: nil)
UIView.animate(withDuration: 0.35) {
// layout
self.view.layoutIfNeeded()
}
// revert button text to 'cancel'
cancelButton.title = "Cancel"
}
}
Here is the output of the debugging flag before the 'back' button is hit:
[BEFORE]
normalActionView.isHidden: false
keyboardActionView.isHidden: true
keyboardUp: true
actionViewActive: true
[AFTER]
normalActionView.isHidden: true
keyboardActionView.isHidden: false
keyboardUp: true
actionViewActive: true
after:
[BEFORE]
normalActionView.isHidden: true
keyboardActionView.isHidden: true
keyboardUp: false
actionViewActive: true
[AFTER]
normalActionView.isHidden: true
keyboardActionView.isHidden: true
keyboardUp: false
actionViewActive: true
As you can see above, the isHidden property of the normal action view is not changing, even though it is being set to 'false'. I originally thought this was because a reference was being lost somewhere (though I thought that would result in a nil reference, which I don't have), so I made the references to normalActionView and keyboardActionView strong. This did not fix the problem, obviously, so I put the changes to isHidden in a method called by a notification to ensure it was always on the same thread (which it is, I checked by printing the current thread, it is always main), but that did not help either.
isHidden behaves as though it is cumulative. For example, if you set it to true twice in a row, you would need to set it to false twice.
I usually use a check for the opposite value before setting a value.
if view.isHidden == false {
view.isHidden = true
}
Why did you bind uiview animation to keyboard notifications ? Is that a required part of your application UI ? I think you can animate your view after a button pressed. Because it's highly difficult to catch the exact keyboard animation speed together working with any view. What I mean is that keyboard closes fast while notification center tries to catch and animate view.
Thanks.

iOS Refresh Accessibility Label

I have a button with accessibility label #"a". When the button is pressed, I have a callback that sets the accessibility label button.accessibilityLabel = #"b". I know this line of code runs. However, if I tap the button again, VoiceOver still reads a. Unfortunately, the code I'm working with is proprietary, so I can't share it directly.
However, in general, I would like to know what issues might cause VoiceOver to not recognize an update to a label.
THE BEST way to handle dynamic accessibility labels is to override the property functions on the views that are being focused (EX: on a UIButton). This allows TWO things. A: it's a lot easier to maintain than setting the property everywhere it can change. B: you can log information and see when the system is requesting that information, so you can better understand WHY things are happening. So even if it doesn't directly fix your issue, seeing WHEN the system requests your value, and logging that data, is inherently valuable.
Doing this in Objective C
#implementation YourUIButton
-(NSString*)accessibilityLabel {
if(someCondition) {
return #"a";
} else {
return #"b";
}
}
#end
In Swift
public class YourUIButton : UIButton
override public var accessibilityLabel: String? {
get {
if (someCondition) {
return "a"
} else {
return "b"
}
}
set {
NSException.raise(NSException("AccessibilityLabelException", "You should not set this accessibility label.", blah))
}
}
}
You could also use this logic JUST to debug, and allow setting and such.
There are a lot of potential issues here. Race conditions, which view is actually getting focus, is there some parent child relationship going on, etc. Overriding the property and adding logging statements to the above code will help you understand what view is actually getting the accessibility label requested and when. Super valuable information!
Use this while changing button text
UIAccessibility.post(notification: .layoutChanged, argument: yourButton)
Try to add UIAccessibilityTraitUpdatesFrequently to your buttons property accessibilityTraits
- (void)viewDidLoad {
myButton.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently
}
Also, when changing accessibilityLabel be sure that you're on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
myButton.accessibilityLabel = #"b";
});
You don't really need a way to refresh the voice over labels. Its done automatically. I have tried this and it works as expected.
class ViewController: UIViewController {
var tapCount = 0
var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
button = UIButton(type: .system)
button.setTitle("Hello", for: .normal)
button.frame = CGRect(x: 10, y: 10, width: 100, height: 50)
view.addSubview(button)
button.accessibilityLabel = "Hello Button"
button.accessibilityHint = "Tap here to start the action"
button.accessibilityIdentifier = "hello_button"
button.addTarget(self, action: #selector(buttonTap(sender:)), for: .touchUpInside)
}
#IBAction func buttonTap(sender:UIButton) {
tapCount = tapCount + 1
sender.accessibilityLabel = "Hello button, tapped \(tapCount) times"
}
}
What Voice oversays:
Hello Button-pause-Button-pause-Tap here to start the action
On button tap
Hello button, tapped one times
Another tap
Hello button, tapped two times

Weird UIButton behaviour in Today Widget

I added a today extension to my app. I edited the vanilla widget a little bit and made it look like this:
Please notice the UIButton titled "Went 1st". When it gets pressed (touched up inside to be exact), it triggers this action:
#IBAction func coinChanged(sender: UIButton) {
if sender.titleLabel?.text == "Went 1st" {
coin = true
sender.titleLabel!.text = "Went 2nd"
} else {
coin = false
sender.titleLabel!.text = "Went 1st"
}
}
It basically alters between two states, changing its title and a variable accordingly.
Here is the problem though - when I press it, it indeed changes its title, but immediately changes it back, ending up on the same title as it had initially. My first thought was the action gets called twice after a press, but when I checked with print I found out it gets called only once. Sometimes the prints didn't even show up in console, but that's a different story.
So, that's one problem. There's one more, though - when I press the button, the whole widget gets misplaced. To know what I mean, look at the first picture (that's the widget before any presses) and now on this one (after the button gets pressed):
You can see that the borders are now on the very edge of TodayView. For reference, here are the constraints of the first segmented control:
Edit: Here are the constraints for "Went 1st/2nd" button:
Edit 2: Be sure to tell me what's wrong if you downvote, so I can avoid making the same mistakes next time
The problem is you should not be setting the button text like that. The title label is primarily used to set text size, font, color, etc. To set the title use something like this:
sender.setTitle("Button Title", forState: UIControlState.Normal)
So the new ib action should look like:
#IBAction func coinChanged(sender: UIButton) {
if sender.titleLabel?.text == "Went 1st" {
coin = true
sender.setTitle("Went 2nd", forState: UIControlState.Normal)
} else {
coin = false
sender.setTitle("Went 1st", forState: UIControlState.Normal)
}
}

Can't switch state of UISwitch

I have to UISwitches in my application. At launch, they're both set to off and the second switch is disabled and should only be enabled when the first switch is aswell, meaning:
#IBAction func switchOneToggled(sender: UISwitch) {
if switchOne.on {
switchTwo.enabled = true
}
else {
switchTWo.enabled = false
}
}
My problem is, that when I enable switchOne, switchTwo gets enabled, but I can't toggle switchTwo's on/off state by touching it.
Try resetting the switches. I had a similar problem and just deleted the switch and replaced it, and giving it the same connections that is had before. It must be a bug inside Xcode, since it should be able to turn on and off without any code.
#IBAction func switchOneToggled(sender: UISwitch) {
switchTwo.enabled = switchOne.on
}
do you maybe have userinteractionenabled set to false for switchTwo?
The problem is that your switchOneToggled is called when the Switch is tapped and the state of the switch is changed. You should check the state of Switch 1 and enable your Switch 2 in your viewDidLoad.

Touch Up Inside not working properly

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.

Resources