MobileMakers Week 2 Day 3
We had another great day. Today actually went by smoothly with no video interruptions. Which was a good thing because students were because kind of bothered by it.
Morning Lesson
Today we learned about custom delegation. We learned that delegation is a design patter that allows objects to communicate with each other and how we can use a custom delegate to our advantage. We also covered briefly the difference between a required and optional protocol. Although when we tried entering a method as optional, our view controller was still showing us that it was required.
We learned that creating a custom delegate can be sometimes tricky and always involves four steps.
- Create a @protocol
- Implement that protocol in a class
- Add a delegate property to the class that the protocol applies
- Set the delegate property to an instance of the class that implements the protocol.
For example:
MagicalCreature class may have an associated MagicalCreatureDelegate protocol. The MagicalCreature will have a @property id<MagicalCreatureDelegate> delegate and a ViewController will implement the delegate protocol (@interface ViewController <MagicalCreatureDelegate>). The MagicalCreature will call the protocol’s methods on its delegate property.
We learned that the key understandin in Custom Delegation is that the delegate property and the self variable in the ViewController are the same.

Side note: I got to show my code for yesterday’s challenge. I am happy to say that I got many positive comments. Hoping this goes a long way!
HappyLunch App
For today’s app we created a simple app. It contained two dynamic tableview cells, one cell for foods and another one for drinks. In the food cell, the cell contained three buttons that displayed three images of different foods. The same for the drink cell but only two type of drinks.
The point of the app was that when you tapped one of the buttons, the image of the tapped button will be displayed above the cells in a UIImageView. One for the foods and one for the drinks.
We began by creating two subclasses of UITableViewCell, one for for the food cell and one for the drinks cell. For each subclass that we created, we made both files a delegate of its respective cell.
For the food cell, we began by adding an @protocol to our header file. In that protocol, we created an instance method with two parameters, one for the cell and the other for the button tapped.
FoodTableViewCell
- (void)foodTableViewCell:(id)cell didTapButton:(UIButton *)button;
Also, after our @interface we created our delegate property.
FoodTableViewCell
@property (nonatomic, assign) id <FoodTableViewCellDelegate> delegate;
We still don’t quite understand the difference between nonatomic, atomic/weak, strong, assign, etc.
In the implementation file we created an IBAction called onFootItemPressed:(id)sender and we passed our custom method and connected it to all three buttons.
FoodTableViewCell
- (IBAction)onFoodItemPressed:(id)sender {
[self.delegate foodTableViewCell:self didTapButton:sender];
}In our DrinkTableViewCell we did the exact same thing but with the custom method renamed.
In our main view controller, we imported the delegates for our tableviewcells, and tableview datasource and delegate.
We then created two IBOutlets for our images.
Here we also learned about constants. We learned that we could create a constant string and define it right after our @implementation. This could save us some time.
We created two constant strings to store the names of our reuse identifiers. In today’s case, we left our viewDidLoad blank. We simply added the required methods for our table view.
In our numberOfRowsInSection, we simply returned “2" to represent both cells. In our cellForRowAtIndex method in order to display both cells, we needed to create two instances, one for each cell. The only way we could return two cells, was by implementing an if statement. We then checked if the indexPath.row equaled zero, we would return our FoodTableViewCell, otherwise return our DrinkTableViewCell. Finally, for each cell we just set its delegate to self;
OrderViewController
if (indexPath.row == 0) {
FoodTableViewCell *cell = [tableview dequeReusableCellWithIdentifier:kFoodCell];
cell.delegate = self;
return cell;
} else {
DrinkTableViewCell *cell = [tableview dequeReusableCellWithIdentifier:kDrinkCell];
cell.delegate = self;
return cell;
}At the end of our implementation file, we called both custom methods and simply passed the image of the selected button and assigned it to the image of our UIImageViews.
OrderViewController
- (void)foodTableViewCell:(id)cell didTapButton:(UIButton *)button {
self.selectedFoodImageView.image = button.imageView.image;
}Here is the link in GitHub: HappyLunch
Challenge
Today’s challenge was a small taste of what we should expect out in the “real world”. We needed to complete an app implementing things that we have never used/seen before. I’m talking about Container Views, UICollectionViews, creating a sliding panel among other things.
The app itself was very simple looking. It was just a main view with three images. When you tapped on the “hamburger” button, it would slide the view to the right and reveal another view controller with two buttons. One for lions and another for tigers. If you pressed on the lions button, it would change the images from three tigers to three lions and viceversa. Nothing else.. that was it… no added functionality to the app.
My Approach
Fairly simple you would say. Well for the half hour or so, I think I just started at the screen thinking to myself how would containers work. Specially since the instructions needed two and set one on top of each other.
Adding the containers was no big deal, just like any other UI element, drag and drop. I then subclassed two view controllers, one for the Top view and one for the button view. I implemented the two view controllers onto my storyboard and renamed them to match the custom classes I created.
The app required for the TopViewController to be embedded in a navigation controller, so that was simple. I then added a UIBarButton item to the top left of the view.
On my TopViewController’s header file, I created a my custom delegate and declared a delegate method called topRevealButtonTapped. I did the same thing for my HUDViewController but here I had two methods instead of one. One method for each button.
The next part took me a while, probably my biggest challenge. I needed to drag IBOutlets from the left and right contraints of my TopViewController’s container view and then change the constants so that the view moves to the right. I think that stopped me on my tracks. As I began to see how I could drag an IBOutlet from the constraint itself I figured that it should be done the same way as any other IBOutlet. My problem was then trying to select the actual constraint and make sure I did it from the correct container. Well after tinkering with it, I was finally able to drag the IBOutlet. I pretty much had to add constraints to my container views first, then resize my container so that I would generate an ambiguous constraint warning and then select the constraint. The clue was also to which viewController to drag to. Of course we could not drag to the TopViewController because our container views were in the root view controller. So I dragged them to my root view controller.
I later discovered that I could rename the container view without affecting its functionality of it.. Now I know. Now moving to the hard part. Moving my view to the right to reveal the bottom view. Thinking about the several solutions we could implement, we could use UIGestureRecognizers to detect the tap of the button, we could also implement a swipe gesture recognizer to just drag the view to the right, we could implement some type of CoreAnimation but then thought of a simpler solution, one of which we had already used in a previous app.
Since we really didn’t want to resize/move our navigation controller or view controller the solution was simple after inspecting the container view. The container view was in fact of UIView type and all UIViews have the animateWithDuration method.
I decided to use that and simply assign new constant values to my constraints.
I knew that the initial value of the left constraint constant was -16.0. I could tell because every time I add layouts it reminds me of the 16 point margin for each side. So I passed an if statement that checked if the left constraint constant was -16.0 I would then assign the new value of how many points I wanted to move my view to the right and of course assigned also a new constant to my right layout constraint since I wanted to simulate moving the whole view to the right not shrinking my view.
In my else statement, I assigned the -16.0 to both constants and doing so, returning my view to its original place. I noticed that just by doing this, the view would not animate, it would just be one solid move. After reading some of the documentation, I found a method I could use so that we could force the layout of the views immediately. I simply called [self.view layoutIfNeeded]; outside of my else statement and that did the trick.
RootViewController
[UIView animateWithDuration:0.5 animations:^{
if (self.leftLayoutConstraint.constant == -16.0) {
self.leftLayoutConstraint.constant = self.leftLayoutConstraint.constant + 116.0;
self.rightLayoutConstraint.constant = -132.0;
} else {
self.leftLayoutConstraint.constant = -16.0;
self.rightLayoutConstraint.constant = -16.0;
}
[self.view layoutIfNeeded];
}];The new constants took me some time to figure out, basically trial an error until it moved to the right to the point I liked it.
After completing this, I added the prepareForSegue method and because I had two segues, I added an if statement to check if the segue identifier was equal to the indentifier of my TopViewController I would segue to that view.
Here however, I ran into a snag. I could tell that the button was pressed successfuly and the method was being called but for some reason it would not display the view. Looking at the older projects of several days ago, I noticed that all the other segues did not include a navigation controller.
I then instead of creating an intance of TopViewController and setting it as my destinationController, I instead created an instance of UINavigationController and assigned it as my destinationController. I then passed the my topViewController as the navigation controllers zeroth index.
if ([segue.identifier isEqualToString:@"topContainer"]) {
UINavigationController *topNav = segue.destinationController;
self.topNavigationController = topNav.viewControllers[0];
}Once I completed that, I needed to add a UICollectionView to TopViewController. Since UICollectionView’s and UITableView’s are similar I quickly found that creating them was basically following the same steps. I added the UICollectionView to my storyboard, connected the DataSource and Delegate to TopViewController. I did find that UICollectionViews have no titleLabel, no subTitleLabel and even no ImageView. I had to resort to subclassing a UICollectionViewCell and adding the UIImageView.
CustomCollectionViewCell
@property (weak, nonatomic) IBOutlet UIImageView *cellImageView;
I did find that the required methods were very similar. So implemented both cellForItemAtIndexPath and numberOfItemsInSection. I then created a NSMutableArray to hold the initial three images I wanted to display. In my header file I also created an instance of NSMutableArray called photosArray. I then assigned my mainArray to my photosArray.
In my numberOfItemsInSection I simply returned the count of items in my photosArray.
In my cellForItemAtIndexPath method, I created an instance of my custom cell, I then assigned each item’s image using the indexPath.row.
TopViewController
CustomCollectionViewCell *cell = [collectionView dequeReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
cell.cellImageView.image = self.photosArray[indexPath.row];
return cell;
I finally added an IBAction for the UIBarButtonItem and passed my delegate method of topRevealButtonTapped.
TopViewController
- (IBAction)onPanelButtonTapped:(id)sender {
[self.delegate topRevealButtonTapped];
}To update the images depending on which button is pressed in my HUDViewController, I used the two methods that were created in the HUDViewController delegate. In each method I created the one array. One array for lions, and another one for tigers. If the tiger button was pressed, I would assign the tigerArray to the TopViewController’s photoArray and then reload the data. Finally in that method, I just called the topRevealButtonTapped to return the view to the original position.
RootViewController
- (void)tigetButtonTapped {
self.tigerArray = [NSMutableArray arrayWithObjects:[UIImage imageNamed:@"tigerOne"], [UIImage imageNamed:@"tigerTwo"], [UIImage imageNamed:@"tigerThree"], nil];
self.topViewController.photosArray = self.tigersArray;
[self.topViewController.imageCollectionView reloadData];
[self topRevealButtonTapped];To finish off, I added an else if statement to my prepareForSegue that would check if the segue identifier was equal to my HUDViewController segue and then set my instance of HUDViewController as the segue.destinationViewController.
RootViewController
else if ([segue.identifier isEqualToString:@"HUDContainer"]) {
self.hudViewController = segue.destinationViewController;
}Here is the link in GitHub: LionsAndTigers