Suppose I have an object called ColorHolder where one of its properties is a UIColor value. Suppose I also have an NSMutableArray that stores some UIColors, let's call it colors. I initialize a ColorHolder object with a value from the array as follows:
[[ColorHolder alloc] initWithColor: [self.colors objectAtIndex:5]];
Now at some point later in the program, the user changes the value in colors at that same index 5. What I would like is the ColorHolder object I initialized above, for its color value to also change when its corresponding value in the colors NSMutableArray changes. How can I set up a pointer in this way so it happens?
The views which you set the font (and the FontHolder whatever that does) are not "observing" changes to the font array and the UIFont class is immutable.
You will need to use NSNotificationCenter to add an observing class which monitors this array and sends notifications to the views (and/or FontHolder(s)) which are interested.
This is a classic observer pattern.
http://blog.csdn.net/dadalan/article/details/4240733
The above blog is not a copy and paste answer for your code, but I provide it for information on the pattern in general.
Also if your really not needing to update views you could simply tell FontHolder to refresh itself from the font array anytime the user has made a change to it. Which if there is only one observer is a cheap way to implement the observer pattern.
Related
I am drawing a tableview via BehaviorRelay.
Currently, I am using the code below as a way to add data.
viewModel.user.append(Person(name: "king", phoneNumber: "12341234"))
viewModel.personObservable.accept(viewModel.user)
I wonder if this code changes the user itself so that the whole tableView is redrawn.
If so, what method can I use to change only the data I added?
The code presented causes the personObservable (which is actually a BehaviorRelay apparently,) to emit a next event that contains an entire array of Person values, not just the latest Person added. Importantly, it's not emitting the viewModel.user object (at least not conceptually) but an entirely different object that happens to be equal to viewModel.user.
The default dataSource, the one that you get when you call items with anything other than a DataSource object, will call reloadData on the table view. This doesn't cause "the whole tableView" to be redrawn though, but it will cause the table view to query the data source for all of the visible cells, even if they haven't changed.
If you only want the table view to load the new cell, then the data source object needs to be smart enough to compare the new array with the array it's currently displaying so it can figure out which values are different and add/remove/move cells as appropriate, instead of just calling reloadData. As #Sweeper said in the comments, the RxDataSources library contains a set of data source classes that have that logic built in. If you wanted to reinvent the wheel, just write a class that conforms to both RxTableViewDataSourceType & UITableViewDataSource and implement the diffing yourself.
I have a data model, composed mostly of an NSMutableArray 'contents' and NSMutableDictionary 'contentsByName'. I have ViewController objects that I wish to observe changes in the data model.
I have a property "count" on the model that returns the size of the array 'contents' and I can trigger a KVO change observation with willChange: and didChange:. So far, so good. However, the view-controllers are now aware that the model has changed, but do not know what's been added to it. Ideally, I need to put extra information into the change dictionary that's delivered to the observer.
Is this at all possible?
This is easily solvable by updating the model objects in a more granular way; however, built-in collections don't generate KVO notifications when their contents are modified, and will require some manual support.
If you want to generate notifications about changes to the array, use willChange:valuesAtIndexes:forKey: and didChange:valuesAtIndexes:forKey: whenever it's modified. When these methods are used, the change dictionary will contain an entry for NSKeyValueChangeIndexesKey, which reflects the indexes of insertion, deletion, or replacement.
If you want to generate notifications about changes to the dictionary, you can call willChangeValueForKey: and didChangeValueForKey: on the dictionary itself, like so:
- (void)addContent:(id)content {
NSString *key = [content name];
[self.contentsByName willChangeValueForKey:key];
[self.contentsByName setValue:content forKey:key];
[self.contentsByName didChangeValueForKey:key];
}
Any observer can also use NSKeyValueObservingOptionNew or NSKeyValueObservingOptionOld to receive the new or old values, respectively.
How does look the practical usage of IBOutletCollection? Unfortunately Apple documentation mentions it briefly without giving an wider idea of usage. OK, it maintains one-to-many relation with IB, but how to access and use particular objects efficiently? With TagName? How to ensure the order of objects?
I've recently used this to easily initialize a grid of labels. I have a n by n matrix of labels on a view, and reference each one individually (via an IBOutlet) in order to display relevant data. However when the view first loads I wanted to control the default text that displayed in all the labels. Initially i wanted a dash to display, but since this is for a client I wanted it to be easy to change. The view contents have and continue to change over time, per client requests.
Instead of writing N line of code, I created an IBOutletCollection and accomplished the same results in 4 (#property, #synthesize, and for loop). YMMV but I found it very useful in this situation.
Read again this section in the Interface Builder User Guide.
IBOutletCollections are actually just NSArrays you can connect to more than one object in IB. All the objects you connected end up in this array and can be accessed from code like any other object in an array.
I used it to minimize code. I have a range of UIViews that should react on "touch up inside" events of some UIButtons (custom mode).
I gave all UIButtons a tag (lets say 1005 to 1010) and all UIViews the same tag as the UIButton they shall respond to.
Then I connected the UIViews with the collection in Interface Builder. All UIButton touch up events go to the same function in my controller. This function gets the tag of the sender object, iterates through the NSArray list (of "IBOutletCollection(UIView)") and compares the tag. Everytime it hits, the appropriate action is done.
It is a pity that NSArrays seem not to hold the order...
The docs say:
The default implementation does not
copy attribute values. If the
attribute value may be mutable and
implements the NSCopying protocol (as
is the case with NSString, for
example), you can copy the value in a
custom accessor to help preserve
encapsulation (for example, in the
case where an instance of
NSMutableString is passed as a value).
So instead of getting into trouble and inconvenience with overwriting accessors in my NSManagedObject subclass, couldn't I simply do something like this?
myManagedObject.firstName = [[firstNameMutableStr copy] autorelease];
This would have the exact same effect, or not? The dynamic implementation would retain that anyways ... so.... why not the easy way?
It's an open question whether having to remember to copy the mutable string every where in code you set the attribute is "the easy way."
With a custom accessor, you just write the copy once then forget about. It copies automatically from that point on.
Just imagine that in thousands of lines of code you forgot to copy just once and developed a subtle bug because that one attribute of the managed object sporadically changed because some other totally unrelated code subsequently changed the mutable string you held only by reference.
I could tell you some stories of weekends lost to debugging because someone took "the easy way."
I apologise if this has been asked before but I can't find the info I need.
Basically I want a UITableView to be populated using info from a server, similar to the SeismicXML example. I have the parser as a separate object, is it correct to alloc, init an instance of that parser & then tell RootViewController to make it's table data source a copy of the parser's array.
I can't include code because I haven't written anything yet, I'm just trying to get the design right before I start. Perhaps something like:
xmlParser = [[XMLParser alloc] init];
[xmlParser getXMLData];
// Assuming xmlParser stores results in an array called returnedArray
self.tableDataSource = xmlParser.returnedArray
Is this the best way of doing it?
No, you don't want to do this. You don't want your view controller directly accessing the array of the data-model. This would work in the technical sense but it would be fragile and likely to fail as the project scaled.
As the projects grow in complexity, you will want to increasingly wrap your data model object (in this case the xmlParser) in protective layers of methods to control and verify how the data model changes. Eventually, you will have projects with multiple views, multiple view controllers as well as information entering from both the user and URLs. You need to get into the habit of using the data-model object not just a dumb store you dump stuff into but as an active manager and verifier of your data.
In a situation like this I would have my data-model's array completely wrapped by making it a #protected or #private property. Then I would have dedicated methods for fetching or inserting data into the actual array inside the data-model class itself. No objects outside of the data-model should actually have direct access to the array or have knowledge of its indexes.
So, in this case your data-model would have something like:
- (NSString *) textForLineAtIndexPath:(NSIndexPath *) anIndexPath{
//... do bounds checking for the index
NSString *returnString=[self.privateArray objectAtIndex:anIndexPath.row];
if (returnString=='sometest'){
return returnString;
}
return #""; //return an empty string so the reciever won't nil out and crash
}
as well as a setTextForLineAtPath: method for setting the line if you need that.
The general instructional materials do not spend enough (usually none) time talking about the data-model but the data-model is actually the core of the program. It is where the actual logic of the application resides and therefore it should be one of the most complex and thoroughly tested class in your project.
A good data-model should be interface agnostic i.e. it should work with a view based interface, a web based interface or even the command line. It should neither know nor care that its data will be displayed in a tableview or any other interface element or type.
When I start a new project, the first thing I do is comment out the '[window makeKeyAndVisible];' in the app delegate. Then I create my data-model class and test it old-school by loading data and logging the outputs. Only when it works exactly how I wish it to do I then proceed to the user interface.
So, think real hard about what you want the app to do on an abstract level. Encode that logic in a custom class. Isolate the data from all direct manipulation from any other object. Verify all inputs to the data before committing.
It sounds like a lot of work and it is. It feels like overkill for a small project and in many cases it is. However, getting the habit early will pay big dividends very quickly as your apps grow in complexity.
Not quite. You want the data source to be an object that implements the UITableViewDataSource protocol; what I would do in this situation is create an object that implements that protocol and parses XML, so that you can alloc-init it, then set the data source to that object and have it update the table view as appropriate. So based off your code (and assuming you're running within the table view's controller):
XMLParserAndDataSource xpads = [[XMLParserAndDataSource alloc] init];
[xpads getXMLData];
self.tableView.dataSource = xpads;
It's probably a good idea to give this class itself a reference to an NSXMLParser object, so you can use that to parse the XML, then provide convenience methods (like getXMLData) as well as the UITableViewDataSource methods for your own use. (If you go this route, you should also make your XMLParserAndDataSource class implement the more useful of the NSXMLParser delegate methods, and use them as appropriate to update your table view.)
I'm a Mac programmer and not an iPhone programmer; but on the mac,
self.tableDataSource = xmlParser.returnedArray is not correct. You are supposed to either bind the table's content to an Array Controller (if iPhone has one?) or set the datasource outlet to your RootViewController.
In your rootview controller, you would implement the methods:
– tableView:cellForRowAtIndexPath:
– tableView:numberOfRowsInSection:
For – tableView:cellForRowAtIndexPath: you would return a UITableViewCell with the data you received from the XML parsing according to the index path like so:
UITableCell *myCell = [UITableCell new];
myCell.textLabel.text = [parsedXMLArray objectAtIndex:[indexPath indexAtPosition:indexPath.length-1]];
return myCell;
(Something people don't know is that you can use the + new class method on all NSObject subclasses which automatically call alloc/init.)
For – tableView:numberOfRowsInSection just return the count of the data array:
return parsedXMLArray.count;
Can't edit my question nor post replies, can only post my response as answer.
#TechZen: I'm somebody who tries to form analogies, helps me understand. What you're saying is something like: My original idea was like going into the file room & dumping all the originals on my desk to work on where as you suggest the object be more like an organised file clerk who will search through the data for me and only return the specific datum that I need while being the only one with direct access to that data.
Have I understood correctly?
#Tim: What if I later need the parser to get data for something which is not a table? That's why I thought to dump it into an array & let the caller decide what to do with the data. Would you suggest a second object that would supply the data in the newly required form? (Am I sort of one the right track here or way off?)