I am trying to use the iOS 13 Combine framework in conjunction with some UIKit controls. I want to set up a viewcontroller that contains a switch that enables/disables a button whenever the switch is toggled on/off. According to Apple's documentation, UIKit controls have built-in support for Combine publishers, etc. so this should be possible.
I have a viewcontroller that contains a UISwitch and a UIButton, as shown here:
link to screenshot of my viewcontroller
and here is my code:
import Combine
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var mySwitch: UISwitch!
#IBOutlet weak var myButton: UIButton!
var myCancellable: AnyCancellable?
override func viewDidLoad() {
super.viewDidLoad()
mySwitch.isOn = true // Set initial state of switch
myButton.setTitle("Enabled", for: .normal)
myButton.setTitle("Disabled", for: .disabled)
myCancellable = mySwitch.publisher(for: \.isOn)
.subscribe(on: RunLoop.main)
.assign(to: \.isEnabled, on: myButton)
}
}
The above code should (or so I thought) emit the value of the switch's .isOn property, whenever that property changes, and assign the value to the button's .isEnabled property. If it is running the way I would expect, that means that when the switch is toggled ON the button title should read "Enabled" and the button should be enabled. When the UISwitch is toggled OFF, then the button title should read "Disabled" and the button should be disabled.
But it does not behave the way I am expecting. The value from the switch's publisher is only emitted once, when the publisher is first set up inside viewDidLoad(). When tapping on the switch to toggle it on or off, it never emits a value ever again. I can tell it is at least emitting the value once, because if I change the initial state of the switch to either on or off, the button is set to the expected state when the viewcontroller is loaded.
Typically you are supposed to keep a strong reference to the publisher, or else the publisher/subscriber will be terminated immediately, so that's why I am holding a reference with the myCancellable variable. But this does not fix the issue, the values are still not emitted when tapping on the switch.
Does anyone have any ideas on how to fix this? This seems like it should be a simple "Hello World" type of example using Combine, and I don't know what I am missing here.
A common mistake is thinking that UISwitch's isOn property is KVO-compliant. Sadly, it isn't. You cannot use publisher(for:) to observe it.
Create an #IBAction in your ViewController, and connect the switch's Value Changed event to it.
Related
I have a UIViewController with several UITextFields. When tap one text field, it should present the barcode scanning view controller. Once the scanning is completed, my barcode scanning viewcontroller is disappearing (used "dismissViewcontroller") and the scanned value should entered into the text field I tapped. This is working fine. I have set the delegate for each text field like this.
[field addTarget:metrixUIViewControllerIn action:#selector(executeScriptOnTextFieldChange:) forControlEvents:UIControlEventEditingChanged];
The problem is this :
Lets say I have set an alert to display inside this executeScriptOnTextFieldChange method. Once I tapped on the 1st text field, then the barcode scanner comes. Once I scanned barcode scanner closes and set the value for the first text field and fire the alert.Thats ok. But then if scanned by tapping the 2nd textfield and the string will set to that textfield and fire the alert related to 2nd textfield also fire the alert related to first textfield as well. I want to stop happening this. Is there any way to disable the delegate for one textfield? This happens because I am refreshing the view in the viewDidAppear. But I have to do that as well. Please help me.
UIControlEventEditingChanged for a textField can fire at many different events that are not even directly related to that textField, but related inderectly.
For instance, when your ViewController is presenting the barcodeScanner it may trigger a "resignFirstResponder" event on the textField. Also when the 2nd textField is tapped, cause the 2nd becomes first responder and the 1st suffers a "resignFirstResponder".
I suggest trying to use a UITapGestureRecognizer in your textField instead. Example:
Swift 4
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.textField.tag = 1
self.textField.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(fireTextField(_:))))
}
#objc func fireTextField(_ sender: UIGestureRecognizer){
let view = sender.view
guard view != nil else{
//Do nothing
return
}
let condition = view!.tag == 1
if condition{
//or do whatever other stuff you need
self.textField.becomeFirstResponder()
}else{
//Whatever for other textFields
}
}
This way, you could use the "tag" attribute to determine which textField is firing and so adjust "condition". You could also filter the flow with a switch using the "tag".
Not sure if any of this will really help as I would need more info about the flow you need to accomplish. Hope it does help!
I have an app I am working on where it shows a balance for users that changes. I would like to display these values as they change and the only way I know how to do that would be with a button. But the values often change on a different view controller so I was wondering if I can set labels equal to a variable that update along with those variables.
Since the values change outside of the view controllers with the labels, the normal way I change labels, using a button, does not apply.
Thanks in advance!
As a general solution, you could achieve this by declaring a property observer in your view controller, example:
class ViewController: UIViewController {
var updatedData = "" {
didSet {
lblData.text = "Data: \(updatedData)"
}
}
#IBOutlet weak var lblData: UILabel!
}
At this point, each time updatedData value is edited, the lblData label text will be updated.
Note that there is also willSet option which is called just before the value is stored, for more information, you could check Swift Properties Documentation - Property Observers.
I have a controller that has a UITableView property:
class OrdersViewController: UIViewController {
#IBOutlet weak var ordersTable: UITableView!
override func viewDidLoad() {
self.ordersTable.isMultipleTouchEnabled = false;
}
However, it's not working. When I try swiping two cells at the same time (using this library https://github.com/alikaragoz/MCSwipeTableViewCell) it still swipes both of them:
According to Apple's documentation about isMultipleTouchEnabled:
When set to true, the receiver receives all touches associated with a multi-touch sequence. When set to false, the receiver receives only the first touch event in a multi-touch sequence. The default value of this property is false.
Other views in the same window can still receive touch events when this property is false. If you want this view to handle multi-touch events exclusively, set the values of both this property and the isExclusiveTouch property to true.
So, even though the table itself won't handle multi-touch, it's child views handle single touches independently.
To achieve what you want you probably have to set isExclusiveTouch on all cells.
I just noticed that setting a UISwitch's isOn in its IBAction causes the IBAction to be called again. So the following code:
class ViewController: UIViewController {
var count = 0
#IBOutlet weak var mySwitch: UISwitch!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
mySwitch.isOn = false
}
#IBAction func buttonTapped(_ sender: UIButton) {
mySwitch.isOn = !mySwitch.isOn
}
#IBAction func switchChanged(_ sender: UISwitch) {
print("\(count) pre: \(mySwitch.isOn)")
mySwitch.isOn = !mySwitch.isOn
print("\(count) post: \(mySwitch.isOn)")
count += 1
}
}
prints the following when the switch is turned on one time:
0 pre: true
0 post: false
1 pre: false
1 post: true
switch is turned off in viewDidLoad
switch is turned on by the user
switch is on now when switchChanged (IBAction) is called
0 pre: true is printed
switch is turned off programmatically in switchChanged
0 post: false is printed
switchChanged is called again by the system
switch is off now in switchChanged, and 1 pre: false is called
switch is turned on programmatically
1 post: true is printed
Why is the IBAction called by the system a second time? How does one get around this, say, for example, when wanting to negate the user's action based upon some internal state? I feel like I am missing something embarrassingly obvious, but I'm pretty sure similar code used to work. Is this an iOS bug? It's being run on an iOS 10.2 iPhone 5s simulator, Xcode Version 8.2.1 (8C1002)
It's interesting to note that when the button tied to buttonTapped is tapped (calling that same method), the switch's IBAction is not called.
Your IBAction is presumably hooked up to valueChanged, which doesn't indicate a particular touch event, just exactly what it says, that the value was changed.
I'd suggest setting a variable called something like var didOverrideSwitchValue = false, set it to true just before setting the new switch value, then when the function is called, check for that variable. If it's set to true, then set it to false and return.
Or, if you wish to negate the new setting only when it's turned on, then you could do if (switch.isOn), and then if so then you can respond to it by turning it off, if required.
I've been battling the same issue and found a workaround...
Check the "selected" property on the sender in your switch handler. I've found that it's true the first time through and false the second time, so you can tell if you're really being called by the user action.
I'm guessing whatever is teeing up the event to fire the second time isn't the switch itself, or maybe this property gets cleared after the first event is handled. Maybe a UIKit guru could chime in.
The UISwitch docs for -setOn:animated: say
Setting the switch to either position does not result in an action message being sent.
Seems clear enough. Feels like an OS bug.
Anyway, this seems to work but it makes me uneasy because I don't fully understand why the problem exists in the first place, nor exactly why this fixes it, and I worry that either could change in a future OS update.
UPDATE
This works fine in my little test app but not in my real app, which has a more complex UI hierarchy with a nav bar, tabs, etc. This just reinforces my uneasiness with this solution.
I have a UIView with a TableView and a Button (Big Button). The TableView has a custom Cell. In this cell there is an "Add" button. I want to animate the first button when the user makes click on the Add button.
This is my schema:
This is my code:
class ProductsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
#IBOutlet var bigButton: UIButton! <- I WANT TO ANIMATE THAT BUTTON
}
ProductCell class
class ProductCell: UITableViewCell {
#IBAction func addProduct(sender: AnyObject) {
//I WANT TO ACCESS THE BIG BUTTON FROM HERE
}
}
Screen example of my app
I've tried to get the parent controller or the superview to get the IBOutlet but the app is crashing allways
Add block properties to your cells which lets them notify your view controller when they have been clicked. In your view controller block code, you can then access the big button.
See my answer to a similar question. Simply replace the switch example with your button. So replace UISwitch with UIButton.
How can I get index path of cell on switch change event in section based table view
So rather than have the cell try and talk to another cell/button, have the cell notify the controller which can then manage the big button changes.
Although I made a comment about using alternate methods you could also employ a strategy below based on updates to a property stored in the current view controller class. You could just as well use property observation on the ProductsViewController but I assume you'd like to keep OOP focused and reduce the size of your controller.
Subclass the ViewController
One could subclass an existing UIViewController and then create a property in the super class that deals with the value that was changed (row tapped). In that subclass you could then do some animation. Because you would be subclassing you continue to obtain all the benefits and methods defined in your existing controller. In your identity inspector point your Class to the new subclass and create any functional updates to your UI using animation.
class ProductsViewController:... {
var inheritedProperty:UIView = targetView {
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
}
}
class AnimatedProductsViewController:ProductsViewController {
override var inheritedProperty:UIView {
//do something interesting if the property of super class changed
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
//you might want to call this method like so
// didSet { animate(newValue) }
}
func animate (view: UIView){
//do animation routine using UIView animation, UIDynamics, etc.
}
}
Property Observation
Whenever the didSelectCell... method is called just set a value to the inheritedProperty. Then add the property observers (see sample code) and react when the property changes (maybe pass a reference to the view you want to animate).
For example: Within the property observer you can just take that view and pass it to your animator function (whatever is going to do the animation). There are many examples on SO of how to animate a view so just search for (UIView animation, UIDynamics, etc).
The normal benefits of separation are encapsulation of functionality and reuse but Swift also guarantees that each set of property observers will fire independently. You'd have to give some more thought to this as to its applicability in this use case.
Do all this things in your viewController
Add target Method to cell's add button in cellForRowAtIndexPath Method
Like This
cell.add.addTarget(self, action: "addProduct:", forControlEvents: UIControlEvents.TouchUpInside)
Define method
func addProduct(button:UIButton)
{
// do button animation here
}