Using property observers to modify UI components in Swift - ios

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
}
})
}
}

Related

iOS MVVM with RxSwft: what is the drawback with viewmodel everywhere?

RxSwft is very suitable for iOS MVVM.
Putting viewmodel everywhere, disobeys Law of Demeter ( The Least Knowledge Principle ).
What is the other drawbacks?
Will it leads to Memory Leakage?
Here is an example:
ViewController has a viewModel
ViewModel has some event signals, like the following back event
class ViewModel{
let backSubject = PublishSubject<String>()
}
ViewController has contentView and viewModel, and contentView init with viewModel
lazy var contentView: ContentView = {
let view = ContentView(viewModel)
view.backgroundColor = .clear
return view
}()
and ViewModel's various subject are subscribed in viewController to handle other part view
viewController is a Dispatch center.
ViewModel is Event Transfer station. ViewModel is in everywhere, in Controller, in View, to collect different event triggers.
the code is quite spaghetti
in ContentView, user tap rx event , binds to the viewModel in viewController
tapAction.bind(to: viewModel.backSubject).disposed(by: rx.disposeBag)
user events wires up easily.
But there is memory leakage actually.
So what's the other disadvantages?
ViewModel doesn't break the Law of Demeter but it does break the Single Responsibility Principle. The way you solve that is to use multiple view models, one for each feature, instead of a single view model for the entire screen. This will make view models more reusable and composable.
If you setup your view model as a single function that takes a number of Observables as input and returns a single Observable, you will also remove any possibility of a memory leak.
For example:
func textFieldsFilled(fields: [Observable<String?>]) -> Observable<Bool> {
Observable.combineLatest(fields)
.map { $0.allSatisfy { !($0 ?? "").isEmpty } }
}
You can attach the above to any scene where you want to enable a button based on whether all the text fields have been filled out.
You satisfy the SRP and since object allocation is handled automatically, there's no concern that the above will leak memory.
You are right, there are some drawbacks, if you want just a data-binding, I would suggest to use Combine instead, since no 3rd party libraries need, you have it already. RxSwift is a very powerful tool when you use it as a part of language, not just for data binding.
Some of suggestions from my experience working with RxSwift:
Try to make VMs as a structs, not classes.
Avoid having DisposeBag in your VM, rather make VC subscribe to everything(much better for avoiding memory leaks).
Make its own VMs for subview, cells, child VC, and not shared ones.
Since your VC is a dispatch centre, I would make a separate VM for your content view and make a communication between ContentView VM and ViewController VM through your controller.

Swift/iOS: using computed property to trigger UI update, which is better: didSet, willSet or set?

The UI in my iOS app is complex enough that I sometimes get confused when I should enable/disable/hide/show some buttons or views. After some thoughts, I think the app has only three states: Idling, Recording, and Playing.
So I created a computed property variable of the enum type AppState, through which I wish to observe the state changes so I can update the UI accordingly. Some of the UI changes include showing or disabling buttons as well as removing custom UIView objects from their super views.
var curState : AppState = .Idling {
didSet {
if newValue != oldValue {
updateUI() // ?? better here?
}
}
willSet(newValue) {
updateUI() // ?? good here?
}
set {
updateUI() // ?? good here
}
}
I have been using the set above to call updateUI() method, it works fine, but I would like to know which of the three observers is better? I haven't tried willSet or didSet, but I am leaning towards using didSet for the reason that I can compare the oldValue and newValue before updating UI. I could be wrong, I am all ears for advice here.
Thanks!
Properties can be two types:
Stored properties, which have a synthesized backing instance variable, and a n implicit get and set definition which you can't override. If you wish to find out when changes occur, you can use willSet and didSet.
Computed properties, which have no backing storage, and require an explicit get and optionally set. If you want to be notified of changes, set is the place to do it.
You can't mix and match, that is, stored properties can't have a get or set declaration, and computed properties can't have awillSetordidSet` declaration.
In your case, it looks like your appState is a stored variable. So indeed, the correct place to put observer logic is in the willSet or didSet. There are trade-offs:
With willSet, your newValue has to be manually passed around to whatever functions need it.
With didSet, your new value is the value of the stored property itself, which all methods can access via self, so there's no need to manually pass it around. However, this comes at the cost of temporarily making your object state inconsistent (because a value has been set, but its updating effects haven't occurred yet), which can lead to subtle state errors you need to look out for.

Perform Selector After Delegate Method Get Called

I have a UITableView combined with a fetched Results Controller. I deployed controller will change content method, and set the delegate of the frc to self.
But in a function I want to nil out it's delegate so that the will change content delegate method won't be called, and change delegate to self again after some operations to make sure other methods work right, just like this:
-(void)function
{
self.frc.delegate = nil;
for (id obj in self.frc.fetchedObjects) {
if ([obj isKindOfClass:[MultiValue class]]) {
MultiValue * multiValue = (MultiValue *)obj;
multiValue.isSelected = [NSNumber numberWithBool:YES];
}
}
self.frc.delegate = self;
}
The problem is, the delegate method (controllerWillChangeContent) will be called after the function method, so after I set self.frc.delegate to self, the delegate method will still be called.
How to solve this? Many thanks.
First of all:
Delegating is a way to customize the behavior of objects. It has similarities with subclassing on a per-instance basis. Would you change the hierarchy of an instance's class while the object is living? (Yes, you would do in some situations – very rarely.)
So the delegate of an object is not a "state" of the object, you should change. It is something like the "kind" of an object. You should recheck your whole approach.
Second of all:
The reason is that all changes are collected and the delegate message is sent afterwards, when you reset the delegate. This behavior is better in most situations. From the docs:
Rather than responding to changes individually (as illustrated in
Typical Use), you could just implement controllerDidChangeContent:
(which is sent to the delegate when all pending changes have been
processed) to reload the table view.
https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/index.html
To your Q:
You should recheck, why you do not want to be informed about changes in a specific situation while you want to be informed in other situations. This looks like a structural smell. If you are really in such a situation, you should recheck this again. If you are still in such a situation, you can set a flag inside the delegate that signals to ignore a change.

UIWindow in Xcode 6 templates

I have a simple question, why is the main (and only UIWindow) on all of the Apple templates in Xcode declared as a var, rather than a let.
var window: UIWindow?
As I understand it we should use let wherever possible, and especially where the instance won't change.
Also using let with objects still allows you to modify their properties (frame etc).
I'm having a little trouble understanding when to use let with object (class types).
I assumed something like CLLocationManager and UIWindow would be perfect examples of when to use let with objects, but Apple don't seem to use let in their code.
Properties defined with the let keyword must have a value by the time the object is initialized. The window isn't created during initialisation, so it can't be a let.
It's the same situation for view controllers - none of the outlets, or indeed the view properties, can be lets, because they don't exist at initialisation, but are created later when the view is required.
let properties don't really work well for UIKit and view controllers - by design, view controllers don't do a lot of work on initialisation, and you can only set up lets at that point. You are absolutely correct in that let properties would be great for things like a location manager or a managed object context, but as it turns out lazy var is often a better bet.
In my (limited, like everyone!) Swift experience, let properties are great for making immutable model classes, and local lets are the default for creating references in-code.
You can use let properties and define them right there, so for a location manager:
let locationManager = CLLocationManager()
Think more about what information you need to create whatever's going into the property. If it can be made from scratch without any context, do it like the example above. If it needs to have a delegate or any other properties passed in, you'll probably be creating or setting it after initialisation so a let isn't appropriate.
If you define window property as let, then UIKit framework will not be able to set window property of your app delegate when instantiating your default view controller from main storyboard.
So the answer is:
If you want to do everything in code manually, you can make window property to be defined with let. This way you must initialize it in init(...) method of your AppDelegate.
Otherwise, if you want to use storyboards and have them instantiated automatically with default view controller then you must define window as var and enjoy routines that apple perform for you.
It is ought to be initialized inside the application:didFinishLaunchingWithOptions: function. That way you can create different windows that suit your needs. For example:
The application may be being launched in the background to perform a task - no need to initialize a window here.
Application may be being launched by a notification - a different window object may be created.
Also, as pointed out by #Keenle, if you're using interface builder to shape your application, the window is created and assigned at runtime. In no way this could be done if the window property was constant.

Binding a view's layout property to an Object controller

I've recently begun working in Sproutcore, which seems to be a really good solution for client side web app development. However, the documentation isn't as thorough or concise as I'd like, so I am struggling with a few things.
Specifically, I'm trying to bind the layout property of one of my views to an object. I've managed this to an extent, in that when the view is rendered it uses the properties from the object, but the problem I'm having is that when the object is updated, the view's dimensions don't change. However, if the change is persistent and I reload the page, it uses the new values.
Is there some sort of limitation in binding layout properties so that they dynamically update, or do I have the wrong approach here?
I'm not sure this approach will work. Controllers are not supposed to have anything to do with a view's properties; controllers are ONLY supposed to proxy objects.
One alternative you might want to consider is using the adjust method defined on the SC.View class. You can have your view observe a property on the model it represents, and then in the observer call
this.adjust('height', 30); // or whatever
I think adjust can also be used like
this.adjust({
height: 10,
width: 20,...
})
without knowing more about what you are trying to do, its hard to say more.
MORE DETAIL
Ok, so one thing about Sproutcore is you need to be careful to not get in the way of the runloop. Unfortunately, there isn't much documentation on this. What I have learned is that you wan't observers to observe things in in their scope only. So here is an outline of what you want to do.
SC.View.extend({
layout: {...}, // initial properties
// binding to the text field that adjusting depends on, NOT on
// this view's content
outsidePropertyBinding: "binding.to.textField",
outsidePropertyDidChange: function(){
var outsideProperty = this.get('outsideProperty');
this.adjust({
// as shown before
})
}.observes('outsideProperty') // <-- this is how you create an observer
});
What I've shown here is how to adjust based on something that is changing outside of this view. I created
1) A binding to the outside property, which SC updates for you
2) An observer on the bound property, that fires as soon as the value is set, and adjusts the view. The observer is observing a property in the view, not out of the view.
Note that if your view is bound to some content, and its a property on that content itself that changes, then you would do it slightly differently. You wouldn't need the binding to the outside property, you could instead just observe '*content.relevantproperty'. The * before content tells the observer that the content object itself might change (if the views content object can change).

Resources