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.
Related
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.
I have added a UI Button inside of a stack view which is inside of a table view in my storyboard. When I click on my button the correct output is printed in my debugger console but there is no indication in the app that the button has been clicked (no default animation). I have tried looking at my view hierarchy and changing all of the parent views to clip to bounds. Any idea why the button is functioning but not being animated to the user?
The quick fix to your problem is to set delaysContentTouches = false for your table view.
According to the Apple Docs,
If the value of this property is true, the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value is false, the scroll view immediately calls touchesShouldBegin(_:with:in:). The default value is true.
See the class description for a fuller discussion.
Alternatively, if you have subclassed the UIScrollView, you can get the same thing done by overriding the following function,
class MyScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
return type(of: view) == UIButton.self
}
}
I'm looking to implement something for voiceover users that is similar to how Apple handles the miniplayer in their Music app.
In the miniplayer, there are a number of accessibility elements: the album artwork, the track metadata, the play and forward buttons. When a user first selects an element within the miniplayer, the voiceover reads "Miniplayer; double tap to expand the miniplayer" before giving the label for the element selected. But if you navigate between elements in the miniplayer, it will just give each element's label, trait and hint. It will only provide the Miniplayer (container level) label and hint when you have moved from an element outside the miniplayer to an element inside the miniplayer.
Being able to give this kind of context to voiceover users seems like good UX design, but how is this implemented? I understand how to group elements together by including them in the myItem.accessibilityElements array, but not how to determine whether the current/previous element that the user has selected is part of the same container.
To inform a VoiceOver user that an element has been selected within a container, you could create a class for each kind of element you have in the container (use of generics maybe ???) to override the methods of the UIAccessibilityFocus informal protocol.
Let's take an example assuming that we have only labels in the blue container hereunder (code to be adapted for other kinds of elements) :
Create a class for the labels including a property for the superview it belongs to.
class EltLabel : UILabel {
var containerView: UIView? {
get { return self.superview }
set { }
}
}
Override the UIAccessibilityFocus informal protocol methods in an extension of the new created class.
extension EltLabel {
override open func accessibilityElementDidBecomeFocused() {
if self.accessibilityElementIsFocused() {
// Actions to be done when the element did become focused.
}
}
override open func accessibilityElementDidLoseFocus() {
if self.accessibilityElementIsFocused() {
// Actions to be done when the element did lose focus.
}
}
override open func accessibilityElementIsFocused() -> Bool {
guard let containerView = self.containerView else { return false }
return (self.isDescendant(of: containerView)) ? true : false
}
}
Don't forget to create outlets with your labels and everything is automatically handled.
class BoutonViewController: UIViewController {
#IBOutlet weak var label1: EltLabel!
#IBOutlet weak var label2: EltLabel!
}
This code snippet to be adapted and included in your code environment will inform the VoiceOver users that they have highlighted an element within a container as desired.
Now, if you want to "determine whether the current/previous element that the user has selected is part of the same container", just add a variable in the view controller that will be incremented/decremented every time an element of the container is focused/unfocused (a boolean variable should be enough).
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 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
}