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).
Related
I usually set all my auto layout code in the updateCOnstratins method of my view controller for the constraints of all the subclasses defining the view. Then in the subviews I place my constraints in the updateConstraints methods there. This makes me have a property of every single view in my class so I can reference it later on after I set translates.... to false. But Im reading that you don't have to set it in updateConstraints. Just not I read an article where the person says an apple engineer said that if the constraints are only made once then you can put them pretty much where ever. Yet, if you have constrains that change during the views lifecycle you place them in updateConstraints? Here are the links http://swiftandpainless.com/where-to-put-the-auto-layout-code/ http://swiftandpainless.com/dont-put-view-code-into-your-view-controller/.
So where should It go? Was this just an old way of doing this and now it has changed?
What you said in your post is what you would generally want to do. Put any constraints that might change in updateConstraints. This also means you should keep a reference to them to be able to update them or remove/replace them. Any static ones can be put after your initialization code (the init method of a UIView or the viewDidLoad method of a UIViewController, for instance). The only real requirement there is you can only add constraints to views that are actually in a view hierarchy together, so anytime after you've added the appropriate views would be fine.
There is usually no reason not to put your constraint creation code in viewDidLoad, which has the advantage of being called only once. For constraints that change, I like to associate that code with whatever directly precipitates the change, such as a change in size class or the removal or insertion of a view.
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
}
})
}
}
I'm somewhat new to MVC and iOS development, and I can't seem reconcile how UI styling fits into this paradigm.
My view of MVC is built using storyboards, and I can apply primitive styling through Xcode's attribute inspector, but anything more complicated I have to use the Controller to style. For example:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated]; // required
// set background color of view
[[self view] setBackgroundColor:[UIColor darkGrayColor]];
}
This seems to be a clear violation of MVC, as I'm applying style logic inside of the controller's code. I find this analogous to writing an HTML app and instead of using style sheets, I write code to apply styles locally in JavaScript. Is this a weakness of iOS or am I just doing it wrong?
Taken from Apple's docs :
some controller objects might also tell a view object to change an
aspect of its appearance or behavior
And it does make sense as the view is supposed to be passive and only reflect the application state as a UI and the controller will "tell" the view if some of its content needs to be changed according to user actions. (e.g background change, visibility of controls etc...)
Actually the screen is your "view", and your "controller" is sending a message to your view to use a different color for the background.
If you had a data object that held the screen color, that could be your "model". In that case, you'd be passing the data from your model to the view through the controller.
You can mix and match the two within your code. If you want you can even manipulate Model details in the Controller as well. The MVC is not strictly enforced in the general guidelines of the pattern, but if you want to stay true to the paradigm you just have to refrain from using M or V in C.
View entails cosmetics and aesthetics of the forms as well as styling, but bottom line, you can manipulate these facets from the controller...
As others said, if you understand colors and styles as data, it is ok to let the controller take care for it.
But you also could subclass UIviews and internally set the style.
Let's say, you have a TrafficLightView : UIView with a property id trafficLight. you could overwrite the setter and set the background color of the view accordingly to the state of the object. trafficLight.
My class extends Manager and calls setPositionChild(Field f) to change f's position. After calling setPositionChild() method, how do I apply the position(i.e. re-layout and re-paint) so I can see the changes?
I tried to call invalidate(), which did not work.
invalidate() just forces a repaint, it doesn't redo the whole layout, as you noticed.
You can force a relayout by going up to the parent Screen object, and calling invalidateLayout(). Forcing the layout will almost certainly call setPositionChild() on the field you are trying to move, so you will want to make sure the new field position is preserved by the manager layout code. This may mean you need to write a custom manager.
I have a custom field I have created that loads images from a url. What I would like to do is have the field take up no space and then when the image is loaded resize itself to the size of the image. I have almost everything done but I can't work out a way to get the layout to be re done after the image is loaded. It works if I specify the size of the image beforehand. Calling invalidateLayout on the parent will not work as the screen is visible, but just calling invalidate does nothing. What steps to I have to go through to make a field resize?
It would also be preferable if I could call the method on the custom view rather than the parent but this is not essential.
This is for blackberry 4.5.0.
First of all, you will have to invalidate the parent manager, because it does need the new size of your custom field in order to redraw correctly the whole manager. (If there is other fields in the manager after your custom one, or even a scroll).
In the top of my head, here's two solutions you can try to implement :
When your custom field is done downloading the image, call the parent manager to invalidate all fields in it (you will have to hold a reference to the parent manager in your custom field)
-- or --
create the custom field object, without adding it to the manager. Start downloading you image, when it is done, call a parent specific method that will add the custom field to the manager (you will still have to hold a reference to the parent manager in your custom field) (You can use insert if you want to add it between two field already present on the manager). Like that you will not have to resize the field, but only add it to the manager when it is ready to be shown.
Answer to your comment :
Then you should use the synchronized scope :
synchronized(UiApplication.getUiApplication().getEventLock())) {
// UI Code here
}
Basically in this scope, you should only use an invalidate, do your size change somewhere else, before this call.