Technology Musings

May 12, 2012

General / Why I Won't Be Participating in StackOverflow.com Anymore

JB

I have historically been grateful for stackoverflow.com, but I think that perhaps they may be getting overly full of themselves and self-conscious.

I have long used their site to get answers, especially to iOS questions.  It always seems that someone answers any question I might have.  I've always wanted to give back, but found that nearly every questions someone else has already answered.

So the other day I was trying to figure something out, and the answer on StackOverflow was that it was not possible.  I spent a day messing around, and actually figured out how to do it (read about it here).  So, having spent most of the morning trying to find if anyone had accomplished this, I actually found three different StackOverflow.com questions pertaining to it, as well as several sites on the Internet.  So what did I do?  I decided the best way to let people know about the answer was to write a blog post about it, and then point people to the blog to find the answer in each question.  This was going to be my way of giving back.

So what happened?  Well, two of my comments got deleted from StackOverflow, and one had a comment from a moderator attached accusing me of self-promotion!!!!  Because I solved a problem that had been outstanding for a year, and told people the answer!!!!

So, in the future, I will just post to my blog, and leave StackOverflow out of it.  You can come here for the answers.  I don't have enough brain space to deal with that kind of junk.

May 10, 2012

General / Engineering and Metaphysics 2012 Conference

JB

For those interested in engineering and theology and philosophy, this conference is for you!  I've got two talks slated for the conference - come and listen!  Lots of fascinating stuff from a number of disciplines:

www.eandm2012.com

You can see the abstract list here and the conference flyer here.

May 10, 2012

Platforms / iOS - using storyboard relationships for custom view controller containment

JB

One thing that iOS forgot to do with the release of storyboards and iOS 5 is support for custom "relationship" segues.  In addition to normal segues, UINavigationController and UITabBarController have what are called "relationships", which allow you to define containment information for these controllers.  So, for the navigation controller, you define the root view controller, and for the tab bar controller, you define the list of view controllers.  This is all done in a neat interface in the storyboard editor (which is the replacement for interface builder).

Another cool feature in iOS is "containment", which is basically a way of letting you write view controllers which contain other view controllers.  This was not really allowed in iOS 4, but they've added some basic support for it.  Unfortunately, the way that you might want to use containment with storyboarding is through relationships, but iOS doesn't provide a way to do custom relationships in storyboards - or even their default ones on custom view controllers.

My own task was to create a tab bar controller with the tabs at the *top*.  I had a few ideas on how I might approach this problem.  Subclassing UITabBarController didn't seem like a good idea.  So I tried several others.

One possible way was duck typing.  I thought that perhaps I could pull a fast on on the storyboard editor.  What I would do is define my own class that mimicked UITabBarController without actually inheriting from it.  Then, I could create my relationships with the storyboard editor, and then substitute my own non-tab-bar controller at the last minute.  This failed miserably.  

Firts of all, the storyboard editor doesn't like you to substitute non-subclasses for the class that it wants.  If you try to set it to a non-subclass of UITabBarController, it won't give you an error, but it won't save the change, either.

I tried to work around this by making my class inherit from UITabBarController while I was editing the storyboard, and then change it back for the compile.  This worked momentarily, but in the end, it turns out that the "relationship" segues aren't set via getters and setters.  I imagine there is an internal API that is being used.

Therefore, I next tried overriding initWithCoder and reading the nib values myself.  Unfortunately, I was not successful at guessing the keys for decoding.

Then, it hit me.  What I need to do is to mix subclassing UITabBarController with not mixing UITabBarController.  So here's what I did:

class 1: MyTabBarController

This is just a shell class, but it inherits from UITabBarController.  

class 2: MyTabBarControllerImplementation

This is your real implementation class, and only needs to inherit from UIViewController.  Make it work like you want it to.

What you are going to do is designate your tab bar controllers to be of type MyTabBarController in the storyboard editor.  Then, when viewDidLoad is called on MyTabBarController, the view controller de-registers the view controllers from itself, creates an instance of MyTabBarControllerImplementation, moves the view controllers over there, and then replaces itself with MyTabBarControllerImplementation.

Anyway, here's the code.  It's really rough, but works at the moment.  I'm open to improvements:

MyTabBarController.h:

#import <UIKit/UIKit.h>
@interface MyTabBarController : UITabBarController
@end

MyTabBarController.m:

#import "MyTabBarController.h"
#import "MyTabBarControllerImplementation.h"
@implementation JBTabController
- (void)viewDidLoad
{
    [super viewDidLoad];
    JBTestContainerViewController *vc = [[JBTestContainerViewController alloc] init];
    NSArray *vclist = self.viewControllers;
    [self setViewControllers:[NSArray array] animated:NO];
    vc.viewControllers = vclist;
    NSMutableArray *navlist = [self.navigationController.viewControllers mutableCopy];
    [navlist replaceObjectAtIndex:(navlist.count - 1) withObject:vc];    
    [self.navigationController setViewControllers:navlist animated:NO];
}
@end

MyTabBarControllerImplementation.h:

 

#import <UIKit/UIKit.h>
@interface MyTabBarControllerImplementation : UIViewController<UITabBarDelegate>
@property (nonatomic) int selectedIndex;
@property (strong, nonatomic) NSArray *viewControllers;
@property (strong, nonatomic) UITabBar *bar;
@end

MyTabBarControllerImplementation.m:

 

#import "MyTabBarControllerImplementation.h"
@implementation MyTabBarControllerImplementation
@synthesize viewControllers = _viewControllers;
@synthesize selectedIndex = _selectedIndex;
@synthesize bar = _bar;
-(CGRect) subviewFrame {
    CGRect f = self.view.frame;
    return CGRectMake(0, 30, f.size.width, f.size.height - 30);
}
-(void) reloadBarInfo {
    NSMutableArray *itms = [NSMutableArray arrayWithCapacity:_viewControllers.count];
    for(UIViewController *vc in _viewControllers) {
        UITabBarItem *itm = [[UITabBarItem alloc] initWithTitle:vc.title image:nil tag:0];
        [itms addObject:itm];
        [vc didMoveToParentViewController:self];
    }
    _bar.items = itms;    
    _bar.selectedItem = [itms objectAtIndex:0];
    _bar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    UIViewController *firstVC = [_viewControllers objectAtIndex:0];
    firstVC.view.frame = [self subviewFrame];
    firstVC.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:firstVC.view];
}
-(void) viewDidLoad {
    self.bar = [[UITabBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 30) ];
    for(UIViewController *vc in _viewControllers) {
        [self addChildViewController:vc];
    }
    _bar.delegate = self;
    [self.view addSubview:_bar];
    [self reloadBarInfo];
}
-(void) tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
    int idx = [_bar.items indexOfObject:item];
    UIViewController *newVC = [_viewControllers objectAtIndex:idx];
    UIViewController *curVC = [_viewControllers objectAtIndex:_selectedIndex];
    if(newVC == curVC) {
        return; // Don't switch - same one selected!
    }
    [curVC.view removeFromSuperview];
    newVC.view.frame = [self subviewFrame];
    newVC.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:newVC.view];
    [self transitionFromViewController:curVC toViewController:newVC duration:0 options:0 animations:nil completion:nil];
     self.selectedIndex = idx;
}
@end

And there you have it!