What is the syntax for binding a function to a toggle button? - rx-cocoa

The following is a snippet of tutorial code that binds a toggle switch to an activity indicator:
aSwitch.rx.value
.map { !$0 }
.bind(to: activityIndicator.rx.isHidden)
.disposed(by: disposeBag)
aSwitch.rx.value.asDriver()
.drive(activityIndicator.rx.isAnimating)
.disposed(by: disposeBag)
What is the syntax of binding to a private func()?
I want to be able to several things beyond merely flipping a boolean value.
Specifically, I would like to:
Toggle the title of another button;
Enable & clear a UITextView; and
Change a UILabel text.
Or is it better in this case, to merely use the familiar toggle-button #IBAction paradigm?

If the title of another button is dependent on the value of your UISwitch, then you should do something like:
let buttonText = aSwitch.rx.value.map { $0 ? "Button is on" : "Button is off" }
and then bind (or drive, if you're using Driver) your button.rx.title to the this Observable. A similar approach is needed for things like clearing a UITextView or changing the text on a label.
If your UIView elements depend on more than just the state of your toggle, then a more complex structure is needed than simply using map, e.g. by using withLatestFrom, combineLatest or other operators (see: http://www.rxmarbles.com). Since this introduces a bunch of new complexity, is it common to contain this logic in a so-called ViewModel. There are plenty of articles detailing how a so-called MVVM pattern works with rxSwift.

Related

Apple Combine add TapPublisher to UIView

Can I add a tap publisher to a UIView or UILabel? UIButton has tapPublisher built in. I tried making an extension, but extensions can't store properties.
Also, is it bad practices to try and use a tap publisher on anything that isn't a button?
func bindViewModel() {
cancellables = [
continueButton.tapPublisher.sink { [unowned self] in
viewModel.action
.send(.question)
},
headlineLabel.tapPublisher.sink {
// Error: "Value of type 'UILabel' has no member 'tapPublisher'..."
})
]
}
Actually UIButton doesn't have it built in. I suggest you're using extension from Building custom Combine publishers in Swift
For the button it's pretty clear, inside you just subscribe to .touchUpInside action.
But with UILabel or UIView it's not that clear: you have to add your gesture recogniser, make sure that you won't add one more if you've already added it.
You can check gestureRecognizers, but there's still a chance that it's not yours gestureRecognizer you're trying to subscribe to. You can store yours in an associated object, but this not the best solution in terms of performance.
For UILabel you also need to turn on userInteractionEnabled.
I'd say it's too much logic for an extension, but it's up to you.

Automatically detecting UIButton clicks throughout a View Controller for designing an Analytics SDK - iOS

In our app, we need to detect each and every user click on all the UIButtons inside each and every ViewController. Our project is massive, with many UIViewControllers, each having many UIButtons inside. I have added a analyticsEvent String to the UIButton class inside an extension using Objc's AssociatedObjects, and the desired behavior is to send that string to the server once the button is clicked. I would like to know how would you go about handling this.
1- Adding a click-target for each and every button manually. I don't like this, takes ages. And it makes the code base smell like a swamp.
2- Make a custom UIBUtton instance, do something in it and have all the UIButtons in the app use that. I don't like this, as there are many buttons and it'd need massive refactoring.
3- Override some base function using extensions to understand when a UIButton is tapped. For example, I tried this:
extension UIButton {
open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let result = super.point(inside: point, with: event)
if result {
print("SangriaButtonBounds", "Send event to the server")
}
return result
}
}
This is dangerous, and sometimes gets fired multiple times, which is expected as its usage is for sth else.
4- Another thing is to maybe add a click-target whenever the button is initialized using extensions, but some parts of the codebase are sometimes deleted all the targets on an Object and adding them, and it'd need special handling in such case.
I guess there is some function out there which I can add some behavior to it using the extensions to it, but not sure what it is.
I would like to know what would be the best approach to this in your opinion.
I hate to tell you this, but the correct answer is to override and create a custom UIButton subclass where you can put custom logic in.
What you're doing is attempting to add custom functionality to all objects of a certain type, this is exactly what OOP is designed to be great at.
It's a big job to be sure, but doing anything else like overriding something lower-level like UIControl could lead to extremely hard-to-find bugs every time someone tries to touch, for instance, a UISwitch and gets a side effect.

How update a SwiftUI List without animation

I want to update a SwiftUI List without any insert animation.
My List is getting its data from an #EnvironmentObject
I already tried to wrap the List itself and the PassthroughSubject.send() in a withAnimation(.empty) block but this does not help.
A very very dirty workaround is to call UIView.setAnimationsEnabled(false) (yes, UIKit has impact on SwiftUI), but there must be a SwiftUI-like way to set custom insert animations.
While the answer provided by DogCoffee works, it does so in an inefficient manner. Sometimes we do have to force the system to do what we want by being inefficient. In the case of implicit animations in SwiftUI, there is a better way to disable them.
Using the Transaction mechanism in SwiftUI, we can define an extension that can be applied to any view. This will disable animations for the view and any children.
For the list view example, this avoids replacing all the data in the list with a new, but identical copies.
extension View {
func animationsDisabled() -> some View {
return self.transaction { (tx: inout Transaction) in
tx.disablesAnimations = true
tx.animation = nil
}.animation(nil)
}
}
Try applying this extension to your list, or the parent containing view. You may have to experiment to find which view is ideal.
List {
// for each etc
}.animationsDisabled()
This works, just place .id(UUID()) at the end of your list
List {
// for each etc
}.id(UUID())
Sort of like reloadData for UIKit
on tvOS this works for me:
List {
...
}
.animation(.none)
.animate(nil)
you can find more info on https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions

What is the purpose of accessibility and traits?

I would like to find out the purpose of accessibility and traits.
What is the purpose of traits's list of properties list here.
An Accessibility Trait allows you to choose the best description for what an element in your application does.
accessibilityLabel
The accessibilityLabel for an element is read by VoiceOver, and is designed to be a quick, one or two word label for what the element is. For instance, a “share” button may have an accessibilityLabel of “Share”. An “email” button may say “Email”. You get the idea. The goal is to give a brief word or two to give the user an understanding of what the element is and/or does. To implement, just go ahead and set the #property on the element:
[self.saveButton setAccessibilityLabel:#"Save"];
accessibilityHint
The accessibilityHint is designed to be a more lengthy description to be ready by VoiceOver. For instance, in the case of the “save” button above, you may want it to say something like “Saves the current information and returns back to the list of articles.” The #property is set similarly to the accessibilityLabel:
[self.saveButton setAccessibilityHint:#"Saves the current information and returns back to the list of articles."];
accessibilityTraits
You don’t have to use this for common UIKit controls, as it comes by default with the traits you’d imagine. But check out Apple’s WWDC ‘13 session on Accessibility for iOS and you’ll see how they adjusted the traits for some buttons.
AccessibilityTraits can be OR’d together to return multiple options, or just return a single one. As with the others, you can override this in a custom subclass or set it via it’s #property:
- (UIAccessibilityTraits)accessibilityTraits {
return UIAccessibilityTraitsButton;
}
Check ThisLink for more information

Using property observers to modify UI components in Swift

I have created a subclass of a UICollectionViewCell that shows some information. I have one property in with type Weather. When an instance of that is set I want to update the cell. Is the approach below bad? I am thinking of that I may trigger the view to be created to early if I access the UI components before it is loaded. Or is that non sense and only applies to UIViewController (with regard to using view property to early)?
If this is bad, what would be the correct way?
var weather: Weather? {
didSet {
if let weather = weather {
dayLabel.text = dayFormatter.stringFromDate(weather.fromDate)
// ... more code like this
}
}
}
You may want an else clause, though, clearing the text field if weather was nil. Likewise, if you might update this from a background thread, you might want to dispatch that UI update back to the main thread.
Be aware that this observer is not called when you set weather in the cell's init (nor would be the #IBOutlet be configure at that point, anyway). So make sure that you're not relying upon that.
Also, if Weather is mutable, recognize that if you change the fromDate of the existing Weather object, this won't capture that. (If Weather was mutable, you'd really want to capture its changing properties via KVO, a delegate-protocol pattern, or what have you.) But if you make Weather immutable, you should be fine.
So, technically, that's the answer to the question, but this raises a few design considerations:
One generally should strive to have different types loosely coupled, namely that one type should not be too reliant on the internal behavior of another. But here we have an observer within the cell which is dependent upon the mutability of Weather.
This use of a stored property to store a model object within view is inadvisable. Cells are reused as they scroll offscreen, but you probably want a separate model that captures the relevant model objects, the controller then handles the providing of the appropriate model object to the view object (the cell) as needed.
Bottom line, it's not advisable to use a stored property for "model" information inside a "view".
You can tackle both of these considerations by writing code which makes it clear that you're only using this weather parameter solely for the purpose of updating UI controls, but not for the purposes of storing anything. So rather that a stored property, I would just use a method:
func updateWithWeather(weather: Weather?) {
if let weather = weather {
dayLabel.text = dayFormatter.stringFromDate(weather.fromDate)
// ... more code like this
} else {
dayLabel.text = nil
// ... more code like this
}
}
And this would probably only be called from within collectionView:cellForItemAtIndexPath:.
But, this makes it clear that you're just updating controls based upon the weather parameter, but not trying to do anything beyond that. And, coincidentally, the mutability of the weather object is now irrelevant, as it should be. And if the model changes, call reloadItemsAtIndexPaths:, which will trigger your collectionView:cellForItemAtIndexPath: to be called.
There are times where a stored property with didSet observer is a useful pattern. But this should be done only when the property is truly a property of view. For example, consider a custom view that draws some shape. You might have stored properties that specify, for example, the width and the color of the stroke to be used when drawing the path. Then, having stored properties for lineWidth and strokeColor might make sense, and then you might have a didSet that calls setNeedsDisplay() (which triggers the redrawing of the view).
So, the pattern you suggest does have practical applications, it's just that it should be limited to those situations where the property is truly a property of the view object.
I would use a property observer if I planned up updating the value during the users session. If this is a value that only gets updated when the user first loads, I would just simply call a method when my view is initially loaded.
If you use a property observer, you can give it an initial value when you define it so the data is there when the user needs it. Also, if you're updating the user interface, make sure you do it on the main queue.
var weather: Weather = data {
didSet {
dispatch_async(dispatch_get_main_queue(),{
if let weather = weather {
dayLabel.text = dayFormatter.stringFromDate(weather.fromDate)
// ... more code like this
}
})
}
}

Resources