08 Jan 2010
In the last post I showed you how to build a basic sidebar using CPOutlineView and all its data source methods. In this post I am going to continue that idea and show you how to update a content view based on the user's selection in the sidebar. You can see a demo of the final application to get the idea. All the source code created for this post can be found here.
Quick Note: I am using Atlas in this post to layout my basic view and make some connections. If you are not part of the Atlas beta, or do not yet have Atlas, the following ideas should still extend to your application.
Setup the Interface
In Atlas, create a new Cib-based project. Then open Resources > MainMenu.cib to begin putting together the view. We are going to create a basic application with a sidebar on the left and a content view on the right. I like to use a Vertical Split View for this. Go ahead and drag a Vertical Split View into your window. Using the Size tab, make the Split View take up the entire window and resize with the window (selecting all the resizing arrows should do the trick). Also, in the "Split View Size" section, select the resizing arrow for the right view. The right view will be our content view, and this arrow tells the right view to resize horizontally as the window resizes. This is the behavior we want.
Next, we want to create our outline view in the left view of the Split View. Unfortunately, CPOutlineView isn't supported by Atlas yet, so we need to do this programmatically. However, we can save ourselves some work by creating the CPScrollView in Atlas that the Outline View will need. Drag out a Scroll View and put it in the left view of the Split View. Again using the Size tab, make the Scroll View take up the entire left view and resize as it resizes (select all the resizing arrows). Also, in the Attributes tab with the Scroll View selected, uncheck "Shows Horizontal Scroller" and check "Autohides Scrollers". These do exactly what they sound like they do.
Click "Test" in the toolbar to test your interface. Make sure to resize both the window and the split view to see if your resizing flags are set up correctly.
Setup the Sidebar
I showed you how to build a sidebar in my previos post. I'm not going to repeat that here. However, I want to highlight some different techniques that I am using.
Last time I just used CPStrings as my items in the sidebar. However, you can use whatever object you like. For this demo, I created a SidebarColorItem object to wrap all the details of my sidebar items--basically just a name, which the Outline View will display, and a color that will be the background color of the content view when the item is selected. The only thing you need to change from the previous post's code is the
- (id)outlineView:(CPOutlineView)outlineView objectValueForTableColumn:(CPTableColumn)tableColumn byItem:(id)item
of your data source. Instead of just returning item, you will want to return [item name] (the string that will be displayed in the outline view).
Also, I wanted my Outline View to show all of its top-level items as expanded when it is first loaded. I used the following method for that:
- (void)expandAllItems
{
var allItems = [items allKeys];
for (var i = 0; i < [allItems count]; i++)
{
[outlineView expandItem:[allItems objectAtIndex:i]];
}
}
Finally, since I am using Atlas, I want Atlas to instantiate my SidebarController for me. To do that, go back to MainMenu.cib, drag a "Custom Object" into the object section (the area along the bottom of the window). With the new object selected, change its class in the Class tab to SidebarController. Also, connect the outlet sidebarScrollView of the SidebarController to the Scroll View in the left view.
This won't work on its own because our project doesn't know where that file is coming from. So in the AppController, @import the class file.
I kind of breezed over most of the implementation details of the SidebarController, so you may want to take a look at the full SidebarController and SidebarColorItem source.
Create the Content View Controller
Now it is time to create the ContentViewController. For this demo, all it will do is change its background color based on the selection in the sidebar. You can probably imagine a more complex application that will swap out the content view instead. But this should help you get the idea.
The ContentViewController will have an outlet which references the right view of the Split View. This is the view whose background color will be changed.
In the - (void)awakeFromCib method (which will be called when our object is instantiated from MainMenu.cib), all we need to do is register our object to receive notifications when the Outline View's selection changed, specifically we want to know about the CPOutlineViewSelectionDidChangeNotification notification. This is done like:
[[CPNotificationCenter defaultCenter]
addObserver:self
selector:@selector(outlineViewSelectionDidChange:)
name:CPOutlineViewSelectionDidChangeNotification
object:nil];
If you pass it a non-nil object, your selector will only be called when the notification is posted from that specific object. Also, you can specify whatever selector you want, but in this case I called it - (void)outlineViewSelectionDidChange:. This method will be called with the CPNotification object that was posted by the CPNotificationCenter. The implementation of this method may look like:
- (void)outlineViewSelectionDidChange:(CPNotification)notification
{
var outlineView = [notification object];
var selectedRow = [[outlineView selectedRowIndexes] firstIndex];
var item = [outlineView itemAtRow:selectedRow];
if ([item color])
{
[contentView setBackgroundColor:[item color]];
}
else
{
[contentView setBackgroundColor:[CPColor clearColor]];
}
}
In the method you will notice that you can get the outline view through the method [notification object]. Then, you get the selected row of the outline view and finally the selected item. In your application you can do what you like with this item; I just change the background color of the content view.
Now go back in MainMenu.cib, drag out a new "Custom Object" and set its class to be ContentViewController. Now connect the contentView outlet to the right view of the Split View. Also, don't forget to @import the class file in your AppController.
Putting it All Together
As a quick recap, you have:
- Used Atlas to setup a simple interface with a sidebar and a content view, wrapped with a split view
- Created a
SidebarController that is in charge of creating and managing the items in an outline view
- Also created a small
SidebarColorItem for the items in the sidebar
- Finally, created a
ContentViewController that responds to the CPOutlineViewSelectionDidChangeNotification sent by the outline view
Now when you load the final application you should be able to select items from the sidebar and see the background color of the content view change. Here is the demo of my finished application.
As you can see, it is relatively easy to create a simple application with a sidebar and a content view. While this example is fairly contrived, I'm sure you can envision how you can extend it for your own applications.
As always, the full source code for this application is available at my GitHub account: here. If you have any questions, please post them in the comments section.
Stay tuned for my next post on how to style your Outline View to be a little less bland!
23 Dec 2009
I started chandlerkent.com about four years ago as a way to learn how to create a website, including a backend powered by PHP and MySQL. At that time I created really basic blog software. This had many obvious problems, mainly it became impossible for me to maintain and add the features I needed. So the blog became stagnant. But from the experience I gained while building the site, I was able to create and deploy iPhlickr, which was also backed by PHP and MySQL. This site has basically been used for iPhlickr since then. About a year ago I deleted the blog altogether as the information was simply out-of-date and the site was an eyesore. I didn't want to associate myself with that work.
This year I decided I wanted to start blogging again. But I wanted to do it in such a way that would be more sustainable for me in the future. This meant it would need to be drop-dead simple for me to update, maintain, and add features. So I started looking into my options. I looked at WordPress, but it seemed like a lot of overhead. I've also heard that WordPress had some security issues in the past, as well as being a notorious CPU hog. I wanted a little more control over my content that WordPress also didn't offer. So I kept looking. Google's Blogger seemed to offer more benefits. It was very simple to set-up, offered an API that allowed me to use other tools to blog instead of the crappy web interface, and allowed me to host it on my own domain.
I decided to give Blogger a try, and last month I created the first post on the new blog. But I was immediately underwhelmed with what I could do. I wanted more control over my users' experience than what Blogger offered, and the templates provided were downright ugly. So I started to look for another solution. Enter Jekyll.
Jekyll gives me a lot of the benefits I was looking for. It is drop-dead simple to use. I can write using Markdown, Textile, or just HTML if I wanted to. I have complete control over the content, including layout and final HTML markup that will be served to the users. I can easily use source control for my writing like I do for code. And since it just generates static HTML pages, deployment is simple and flexible, and the final product is as fast as the server can deliver simple HTML files. Perfect.
I spent today setting up and configuring Jekyll. Following the Jekyll-wiki was simple and the documentation is detailed. It took me less than an hour to get a simple blog setup. Next, I threw on some simple CSS (powered by Sass--I never want to write straight CSS again!) for style and layout, added a few features like an Archive page and a RSS feed, and uploaded it to my GoDaddy hosting. It was really a joy.
After spending the day with Jekyll, I'm fairly confident I will be using it moving forward. I was surprised with its simplicity and flexibility which work together to allow for a lot of power and control. While I'm not sure when I will next get to work on the site, I do have many features in mind, such as:
- comments (possibly through Disqus)
- better archives
- tags
- related posts
- better front-page
- better layout
- easier deployment (eventually through a git push)
- ads?
- and more...
If you're interested, the source code for this site is up on GitHub. I look forward to any feedback you have as well as suggestions in ways I can improve the experience of this site.
10 Dec 2009
This past week I was tasked with converting our CPCollectionView-backed sidebar to one using a CPOutlineView. The reason for doing this was threefold. First, a collection view could not support all of the project's requirements going forward (at least not easily). Most notably, we wanted to support quick and easy navigation to common items like projects, glossaries, and some community links. Second, an outline view is more Mac-like in that most desktop Mac applications with a sidebar have some sort of outline view. This would move us closer to a Mac desktop experience on the web and lower the learning curve for new users. Finally, we just could.
Since the release of Atlas, the team has been trying to use Cibs to build our interface as much as possible. But CPTableView and its brethren CPOutlineView are not yet supported in Atlas. So that means I had to do this in straight code. This isn't a big deal (most of our interface is still built programmatically at this time), but it just means I have to be extra careful with configuring the view.
Setting Up the View
Setting up a CPOutlineView is not much different from any other CPView with two exceptions. The first is since CPOutlineView inherits from CPTableView, the column of data is actually a CPTableColumn. So I had to create a CPTableColumn and add it to the CPOutlineView, just like in a CPTableView. Simple enough. The second pitfall is again related to CPOutlineView inheriting from CPTableView. In order to correctly support scrolling, the outline view must be the documentView of a CPScrollView.
To get the scroll view and outline view to actually look like an outline view, some additional configuration must be done. First, make sure to autohide the scroll view's scrollers. Again, an outline view is just a very specific table view, so it has a header view and a corner view. But in a sidebar, those views don't really make since, so I just set them both to nil. You probably also want to set the background color of the scroll view to look like a sidebar. I used e0ecfa, but you may choose any color you wish.
DataSource
If you are coding along, and you're like me and like to refresh your project to see the effects of your changes, you are probably disappointed at this point. All you should see is a nice blue rectangle. That is because we haven't told the outline view what to display yet. This is what the dataSource is for.
After setting the dataSource on the outline view to some object, we must implement the correct dataSource methods. There are four required methods:
- (id)outlineView:child:ofItem:
- (BOOL)outlineView:isItemExpandable:
- (int)outlineView:numberOfChildrenOfItem:
- (id)outlineView:objectValueForTableColumn:byItem:
Most of these methods are pretty self explanatory and I'm not going to go into much detail about them here (you can see NSOutlineView's documentation for more information) . I just want to note a few of the hangups that I had. First, these methods will be called with a nil item for the root object in the outline view. Second, if - (id)outlineView:objectValueForTableColumn:byItem: doesn't get called, you haven't added your table column to the outline view. Finally, if you mysteriously can't get anything beyond the first level of data to display (like me), you haven't actually told the outline view which table column is the outline view's table column. So you need to use - (void)setOutlineTableColumn:, passing in your table column.
That's basically it. The final piece is to actually back your dataSource with some object or objects. Since I only need two levels of data, I am using a CPDictionary where the keys are the top level and the values are arrays of data for the second level.
You can see the code for my finished AppController at http://gist.github.com/251323. I included a bunch of debug statements in the dataSource methods to help understand what exactly is going on. You can see a running version of this project at http://www.chandlerkent.com/code/OutlineView/.
What's Next
My next step is to implement CPOutlineView's delegate methods to be able to tell when the selection changes and which item is selected. Also, I want to have all my items expanded at startup like in Atlas, but just haven't gotten around to figuring that out yet.
I hope this post helps you if you are struggling to get a CPOutlineView in your interface or if you wanted to create a somewhat Mac-like sidebar. Leave a comment below if you have any questions, comments, or suggestions.
24 Nov 2009
I finally got the blog setup again. I am still using Blogger because I don't feel like fussing with installing my own solution. We'll see how far this takes me.
I have a few things I want to write up in the next few days, so stay tuned!
Also, I can't seem to find a good Blogger template, so if anybody has any suggestions, let me know.