should I be creating my objects manually or with InterfaceBuilder? - cocoa

I am relatively new to Cocoa/Xcode and am not sure if I am structuring my application in the most "correct" way possible. I have a number of objects which are active the while time that the application is running. Should I be creating these objects manually or with Interface Builder?
The type of objects I am referring to:
"downloader" which is responsible for downloading files to disk
user interface updater which is responsible for updating the user interface to show the results of the downloaded file
Should I create these objects in IB and set up the connections between them with code?

It's really a matter of personal preference.
In my opinion IB only really good at laying out views, so I tend to only use IB for my view and my view controller, and I create everything else in code in the view controller's viewDidLoad or init method.
In your example, connecting the "downloader" object directly to the interface seems like an MVC violation, so I would keep the downloader out of my xib.
The "interface updater" would be connected tightly to the interface, so it could be in the nib, although unless I had a good reason not to I would probably just put that code into my view controller class.
If you are creating things in code, note that viewDidLoad/viewDidUnload can potentially be called a number of times, as the os loads and un-loads your views when they are not visible to save memory. So only put transient objects there... things that must exist for the life of the view controller should be created in the init/dealloc methods. Part of why I like to do most of my object creation in code, is the finer level of control you have over memory.

I typically build as much as I can in IB, and then switch to code when I run into the limitations of IB. It sounds like you should be able to create the UI you described in interface builder.

Related

Sharing code between NSDocument and UIDocument

I have created a document-based app that uses Core Data. I created the mac version first, and now that it's working properly, I am moving on to create an iOS version of it.
I just can't get my head around how to maximize code reuse between the iOS/mac versions, with respect to the Core data bit, since they don't use the same classes.
My document class that handles saving and such is a subclass of NSPersistentDocument. My intention is that a well-designed model class should work in both environments, especially since I don't do all that much fancy stuff with regards to Core data.
Now, since NSPersistentDocument isn't available in iOS, I hit a wall. I tried to get around this by using #if TARGET_OS_MAC and TARGET_OS_IPHONE and in that manner make it a subclass of UIManagedDocument in the iOS version. That obviously would have been convenient, but I can't seem to make it work like that. And it's really looks quite messy, since there are a lot of other stuff that has to be conditionalized as well.
I also tried building the classes atop of NSDocument/UIDocument instead, implementing the Core data hooks myself, but it also looks quite messy, leaving me thinking it's not the right way to go.
The question:
To me, it seems like a good idea to reuse the same document class between the iOS/mac versions, but maybe I'm being naive.
What's the best way to do this?
Should I forget about the code sharing and create a separate document class for the iOS version that emulates all the methods present in the mac version?
Am I right that the code you're wanting to share is model-related? I suggest refactoring that code to a separate object, which both an NSDocument and UIDocument contain (as rickster suggested above).
I use a DocumentRoot Core Data entity with its own NSManagedObject subclass, but if there are no properties you want to manage using Core Data, you can just subclass NSObject.
This may sound strange, but NSDocument and UIDocument are actually controller classes. (To be specific, they're part of the model-controller.) Their jobs are to load the model, set up windows, and save the model. If you need to provide an interface for higher-level access to model objects, it could be in a document root or model helper class instead.
Similarly NSPersistentDocument's job is to configure the managed object context and the persistent store and handle loading and saving. It doesn't necessarily need to provide a complete interface for accessing the model.
(Bringing this over from my comment.)
In general, the situation where you have two classes which must inherit from different superclasses but which also want to share a lot of code is composition. Put the shared code in a separate class; your NSDocument and UIDocument subclasses can each keep an instance of that class, and message it whenever they need to invoke that shared code. (Though as #noa mentions, you might want to consider whether all of that code belongs in your document class to begin with.)
Of course, then you might end up writing a bunch of methods that read like:
- (id)doSomething {
return [sharedController doSomething]
}
That can get to be a pain... so you might want to look into Objective-C's message forwarding system.

Programmatically changing a connection made in IB (OSX)

I have a simple Document based Core Data app (built around the standard Apple tutorials). At the moment I have a button connected to the add: method of a controller (Sheet Controller) that drops down a modal sheet from the main document window. The connection is made in IB (see below from MyDocument.xib). There is also another controller (Another Controller)
My question is how do I programatically change the buttons action method to point to a method in 'Another Controller' instead of the 'Sheet Controller it is wired up to in IB
Many Thanks
Just use the NSControl setTarget and setAction methods.
There is some special magic associated with making the connections hang together in the first place, but once it's all wired up in the running application a connection is a connection regardless of how it was originally made.
Although -- it's worth considering whether you could allow the responder chain to take care of things rather than explicitly retargeting the action yourself. That, at least in part, is what it's there for...

NSArrayController and referring to a shared, static, Core Data based library

Using this guide I have created a static library (let's call it AppCore) that can be shared between the Mac OS X and iOS versions of one app. This static library uses Core Data and the point of it is to share the model part and schema versioning between different implementations.
I created a NSPersistentDocument based project that will depend on this AppCore. In this project I added a reference to the .xcdatamodel file. Then I created a simple table view with add/remove buttons to edit an array of one entity type with the assisted "new core data entity" item. This created an instance of NSArrayController and the required bindings for the add/remove behaviour.
Now, everything seems to work fine when I'm using the default class for the Core Data entities (NSManagedObject) and I'm able to add new rows using the +/- buttons. However, when I change the entity implementation class to a custom one, I'm getting an error
Failed to create new object
This seems to come from the NSArrayController and it seems to be unable to instantiate the required entity. I can, however, create one in the NSPersistentDocument subclass by:
[NSEntityDescription insertNewObjectForEntityForName:#"SomeEntity" inManagedObjectContext:[self managedObjectContext]]
What confuses me is why the instance of NSArrayController can't. If I understand correctly, the array controller is instructed to create an entity, not class and my guess is that the entities are created with the help of NSEntityDescription class. I could implement my own version of the array controller's add: but then again, it might be that here something is fundamentally wrong. I haven't touched the init:s and the custom entity class implementation is simply for convenience, to access the attributes directly.
I have tried changing the base SDK on the AppCore but without effect. Currently it uses the iOS version but I'm not sure how it should be. This is another question but if unrelated, I might ask it here on a separate question.
So, to summarize, why can't the NSArrayController create an instance of this entity?
Thanks in advance.
Update
This works if I add the SomeEntity class from the AppCore to the dependent project as a reference. This is not the most usable way since modifications to the AppCore has to be propagated to the dependatnt projects also.
Bingo. I missed the "-ObjC" flag for the dependant project's "other linker flags". Now everything works like a charm.

Cocoa Touch App Architecture - MVC controller?

I am quite new to Cocoa and to iPhone programming.
I am using the Xcode Utility Application template to implement a simple app that has:
a view with a text field to collect a username
a view with a connect button to start a connection to a remote site using the
username to get some data via HTTP. The data will be presented as a text string on the screen.
I think this represents my VIEW in the MVC pattern.
I created a simple class to store the username and to do all connection work that represents my MODEL and instantiated it inside the AppDelegate.
Here a really simplified sketch:
It is not really clear to me how can I get data nested deep into subviews (username) or how can I trigger actions in nested parent views (connect button).
My question is:
What is the best/cleanest way to implement this architecture?
How do I implement the CONTROLLER?
Thanks in advance for any help,
Paull
Updating my answer based on comment:
It's in most cases ok to have state in your controller. Like an array or an instance of whatever modelobject you are writing an application for.
I would keep the model object clean of any networking code and put that in the Controller instead though. In this case the ViewController where that connection action is triggered.
Original answer:
It is not really clear to me how can I
get data nested deep into
subviews(username) or how can I
trigger actions in nested parent
views(connect button).
With the utility application template you already have a couple of ViewControllers.
To get references to your UI inside your controllers you need to declare IBOutlets and connect them inside Interface Builder. To respond to actions you need to declare and implement IBActions in your ViewControllers and hook them up in Interface Builder as well. Which you do in Connections pane (2nd from left) in the inspector.

Loading data into a Cocoa view before the application loads

I want to load some data from mysql into my cocoa application view before the application starts.
I am sure that this should happen in the controller so that it can send the required data to the view.
I am looking for a method or common technique that is used for this sort of thing.
Many Thanks
Sounds like you're looking for the awakeFromNib function.
http://www.cocoadev.com/index.pl?AwakeFromNib
Cocoa gives you many places to perform tasks before and after objects are loaded from a nib, but it's important to read the documentation carefully to make sure things are happening in the order you expect. Usually I use the following strategy when I'm working on a Cocoa application:
Where appropriate I implement the
+(void)initialize method, which is called before any instances of a class are created. I'll probably set the app's default preferences here, for example.
In my application controller (app delegate), I implement the applicationDidFinishLaunching: delegate method to load my data file. If this works okay, I then create the window controller(s) and display any windows I want to show at launch.
In the window/view controllers, I override windowDidLoad: or loadView to perform tasks involving objects loaded from a nib. If I need to create any instance variables that don't involve the nib, I also override the init method and do that there.
If I need to do anything in my view objects after they're loaded from a nib, I'll override awakeFromNib.
You can use the - applicationDidFinishLaunching: or - applicationWillFinishLaunching: delegate messages, by implementing one of them in your application delegate/controller, and do whatever initialization you want there.

Resources