Flatiron School Week 5 Mistakes and Lessons

Topics: Autolayout, views, gesture recognizers

Symptom: The gesture recognizer doesn’t work.

Mistake: Making one recognizer and assigning it to four objects.

Each gesture recognizer can only have one owner and each assignment meant that it was just being reassigned to the latest view.

Lesson: Make one gesture recognizer for each object.


Symptom: Constraints were not working.

Mistake: I had assigned constraints to the view.

Lesson: Constraints are sent to the superview. Gesture recognizers are sent to the view.


Symptom: My game’s win conditions were being satisfied when they shouldn’t have been.

Mistake: When comparing the two views’ centers, I checked if the difference was less than a threshold, when I meant to use the absolute value of the difference.

Lesson: Unexpected wins are bugs too. They should be understood as much as bad bugs.


Symptom: My autolayout had more constraints than I thought I had specified.

Mistake: To deal with the iPhone size plus, I had constraints for when trait collections display scale were 3, but had forgotten to wrap the other constraints in an else statement.

Lesson: Those willTransitionToTraitCollection:withTraitCollection: methods can get really complicated and I have to be careful with the large if statements.


Flatiron School Week 4- Hacking together the TinderClone Swipe Feature

The Tinder Swipe

The central part of Tinder’s design is the swiping mechanism. The feature allows people to focus on one match at a time and make a decision before moving on to the next match.  Good for spending more time on matches and good for reducing the speed of data consumption.

I wanted to learn about animations, so this weekend I hacked together a prototype of the feature.

Demo

All the code can be found here along with a video demo. The code is very dirty, but that’s topic for another week.

Process

Step 1. Look up how to do it.

The next step in my TinderClone project is to make the swipeable images. Richard Kim has a really cool blog entry about how to make this feature three different ways. Since I’m taking on this project to learn to code, I took the “build it from scratch” way and tried to follow Nimrod Gutman’s blog. I totally failed. Twice.

It turns out that I didn’t really understand how UIView and UIImageViews worked. So, I decided to play around with them.

Step 2. Read up on UIViews

I read thought through the Apple View Programming Guide. in particular, the view and window architecture. I didn’t really understand bounds very well, so I started an Xcode project called playingWithBounds, that ended up being my project.

Take-aways:

  • Views are made on top of Core Animation layers (CALayer). Views have animation, but go to CALayer to one layer deeper for more control.
  • Subviews are arranged in an array in the superview.
  • UIContentMode is useful for sizing images. I played around with this later.
  • I thought the bounds property might have something to do with Box Sizing in CSS, but it’s much simpler. The bounds change the frame of the box.

UIView properties

  • frame- used for making objects. CGRectMake()
  • bounds- changes the size of the view, but not the center.
  • center- very important for moving objects around.
  • transform- used to rotate, move and scale objects. It’s the finishing touch. The object is drawn and then transformed. When an object is rotated, it remembers what it’s heigh and width were when it wasn’t rotated.
  • alpha- 1 is opaque and 0 is fully transparent. Great for
  • backgroundColor- useful for seeing where the rectangle is.

Step 3. Playing around with the UIView properties

I made single view project where I drew boxes and changed the bounds, and so on to see what would happen.

Step 4. Playing around with CGAffineTranform

When I got to Coordinate System Transformations, I started playing around with rotating and moving boxes.

I made a slider to rotate an UIImageView.

//slider
        CGRect slideFrame = CGRectMake(100, 400, 200, 20);
        self.pictureRotationAngle = [[UISlider alloc] initWithFrame:slideFrame];
        [self.view addSubview:self.pictureRotationAngle];
        self.pictureRotationAngle.minimumValue = 0;
        self.pictureRotationAngle.maximumValue = 2*M_PI;
        self.pictureRotationAngle.value = M_PI/2.0;
 
        [self.pictureRotationAngle addTarget:self action:@selector(rotateImage) forControlEvents:UIControlEventValueChanged];
 
        self.picture.transform = CGAffineTransformMakeRotation(self.pictureRotationAngle.value);

Step 5. Finding the Event Handling methods

Where other solutions mentioned at the top used UIGestureRecognizers, I imagined that these event handling methods could move my picture around too. They used UITouch objects.

I was touching my UIImageView and nothing was happening. Googling around, I found that UIImageViews are not user interactive by default. So, I had to set it.

    self.picture.userInteractionEnabled = YES;

I also set the

What is UITouch?

I didn’t know, so I built the first three methods and NSLogged the touch and event objects. They were very descriptive.

2014-10-27 18:58:26.919 TinderSwipeFeature[65292:2410541] <UITouch: 0x7fdf015533a0> phase: Moved tap count: 1 window: <UIWindow: 0x7fdf01409040; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x7fdf01403730>; layer = <UIWindowLayer: 0x7fdf01414c50>> view: <UIImageView: 0x7fdf01684780; frame = (37.8671 64.9702; 402.124 402.124); transform = [0.99420459268393058, -0.10750454821160646, 0.10750454821160646, 0.99420459268393058, 0, 0]; clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x7fdf01684ac0>> location in window: {256, 332} previous location in window: {255, 332} location in view: {192.38019379786252, 249.92073845470227} previous location in view: {191.38598920517859, 249.81323390649064}

UITouch has a lot of really useful info:

  • Size of the screen: 375 width and 667 points height.
    frame = (0 0; 375 667)
  • The view touched and it’s location
    view: <UIImageView: 0x7fdf01684780; frame = (37.8671 64.9702; 402.124 402.124)
  • Current touch location in view and window
    location in window: {256, 332} 
    location in view: {192.38019379786252, 249.92073845470227}
  • Previous touch location in view and window
    previous location in window: {255, 332}
    previous location in view: {191.38598920517859, 249.81323390649064}
  • Timestamp
    2014-10-27 18:58:26.919

I now had the velocity of touches and I just needed to update the view being touched with a new center value to move it.

That was the key to solving the problem.

I added the other Tinder style animations associated with swiping. All of these were based on using the x location of the touch divided by the width of the phone to calculate a factor that would modify the rotation of the image and the opacity of the overlaps.

Step 6. Playing with contentMode

When I put the picture in the UIImageView, it would not fit in the frame, so I had to set clipsToBounds to YES and change the contentMode from UIViewContentModeScaleToFill to UIViewContentModeScaleAspectFill. UIViewContentModeScaleAspectFit also works but you get white space on the sides.

    self.picture.contentMode = UIViewContentModeScaleAspectFill;
    self.picture.clipsToBounds = YES;

The other options such as UIViewContentModeTop maintain the size of the image, and align the listed edge to the corresponding edge in the box. For example,

  • UIViewContentModeTop aligns the image to the top of the UIImageView.
  • UIViewContentModeTopLeft aligns the image to the top and left of the UIImageView.

Step 7. Animation timing

For the touchesEnded method, I wanted to animate the image moving back into location.

[UIView animateWithDuration:0.4 delay:0 usingSpringWithDamping:0.6 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
                ... reset image
            } completion:nil];

CGAffineTransformIdentity removes all transforms (rotations, scaling, translations).

Step 8. Animation for the picture flying off the screen or springing back

To animate the picture flying off to the side, I set a swipe threshold of 30% of the width.

if (differenceInTouchLocationX > aTouch.window.frame.size.width*0.2) {
            [UIView animateWithDuration:0.5 animations:^{
                viewTouched.center = CGPointMake(self.view.frame.size.width*2, currentLocation.y + 0.5 * speedY) ;
 
            }];
            NSLog(@"%@", aTouch);
        }else if (differenceInTouchLocationX < -aTouch.window.frame.size.width*0.3)
        {
            [UIView animateWithDuration:0.5 animations:^{
                viewTouched.center = CGPointMake(-self.view.frame.size.width*2, currentLocation.y + 0.5 * speedY) ;
            }];
        }
        else { 
             ... //springs back to original location
        }

Step 9. Add the overlap images (Like and Nope)

self.like = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 100, 50)];
 [self.like setFont:[UIFont systemFontOfSize:100]];
 self.like.text = @"L I K E";
 self.like.adjustsFontSizeToFitWidth = YES;
 [self.like setBaselineAdjustment:UIBaselineAdjustmentAlignCenters];
 [self.like setTextColor:[UIColor colorWithRed:47/255.0f green:156/255.0f blue:28/255.0f alpha:1]];
// self.like.backgroundColor = [UIColor blueColor];
 self.like.transform = CGAffineTransformMakeRotation(-20* M_PI/180);
 self.like.alpha = 0;

Boy, I learned a lot by just reading the documentation and Googling. That was fun!

Flatiron School Week 4- Notes to Self

Blogging is an intentional investment in time

One reason I blog is to review what I’d learned that week.

The other goal in this blog is to document my creative process. There are two styles of documenting process.

  1. Make the thing, write about it afterwards.
  2. Record nuggets of information and write the blog entry along the way.

Both have their benefits and drawbacks.

  • The first gets to the answer faster, but misses out the details of the process.
  • The second slows down the discovery process, but maintains an accurate record of events. Errs on the side of too much detail that people won’t care about.

This weekend, I spent more time coding the Tinder swipe feature and didn’t have as much time to blog. I wanted to finish. I found that trying to recreate my process of discovery after the fact was time consuming and demotivating. Perhaps that’s not a good strategy. If I haven’t been taking notes along the way, I should just provide a summary of key points.

That’s probably what people care about anyway.

Flatiron School Week 4- Questions and Answers

Topics: Cocoapods and Core Data

Question: Should I use [array mutablecopy] or [NSMutableArray arrayFromArray:array]  ?

Answer: Both are the same if the array is already made. Stylistically, prefer the latter.


Question: Is order maintained in Core Data?

Answer: No, if you want to maintain order, add an attribute like createdAt and use an NSSortDescriptor to order by created time.

Flatiron School Week 4 Mistakes and Lessons

Topics: Cocoapods, Core Data

Symptom: The search functionality works most of the time but in a small set of users, the search results are empty.

Mistake: Sending an API call in the prepareForSegue method.

Let’s say that we have a view controller that searches Foursquare for restaurants. If I press submit on my search view controller, I’m pushed to a table view controller with the results. I can put the API call to fetch the data from Foursquare in the prepareForSegue call.

99.5% of the the time, it works, but the other 0.5% of the time, the previous view controller can be removed before our API call has come back with the results and we’ll be left with a table view controller with no results.

This is called a race condition and it’s a very annoying problem to fix because it’s hard to detect and I would have to have millions of users like Facebook to have this problem.

With search view controllers, pass the query to the results view controller and send the API call from there.

Lesson: Avoid race conditions always.


Symptom: I was trying to test a tableViewController to make sure that it worked with Core Data by adding 3 test objects: @”Test”, @”Test Test”, and @”Test Test Test”. I was only seeing one of the three items repeated three times.

Mistake: Messing up basics when moving on to more advanced topics.

I wasted a couple hours last week when I messed up the cellForRowAtIndexRow by writing

cell.textlabel.text = self.store.pirates[[self.tableview indexPathForSelectedRow].row];

instead of

cell.textlabel.text = self.store.pirates[indexPath.row];

Lesson: Watch for simple mistakes, when something is not working as expected.

Flatiron School Week 3 Notes to Self on Unit Testing

As I’ve started to work on my TinderClone side project more, I’ve realized the need to have good tests.

Why are tests useful?

Test Driven Development allows you to limit scope and develop in an efficient iterative way.

Tests makes sure things are working when you make changes in other parts of the code.

Tests automate a lot of actions that would be tedious to do manually every time.

When are tests needed?

Tests are especially helpful when you’re trying something experimental and don’t want it to break the rest of your code. (Branching is also a great idea when starting a new feature or something that you’re not sure will work.)

Tests are helpful for debugging large projects.

When are tests not needed?

When I work on smaller projects, I’ve found that tests are not worth the time needed to write them.

How do I tap the back button when testing a UITableViewController in KIF?

I tried for the longest time to click the back button, but I couldn’t reference it using an accessibilityLabel. I found that you need the navigation controller to pop the top view controller.

In the it block:

[nav popViewControllerAnimated:YES];

To get the navigation controller add these lines

In the beforeAll block:

UIWindow *window = [UIApplication sharedApplication].keyWindow;
nav = ((UINavigationController*)window.rootViewController);

Since the navigation controller needs to be declared throughout the whole test. Declare a navigation controller

Between the first describe statement and the beforeAll block:

__block UINavigationController *nav;

Common tests for KIF automated UI testing for iOS

Example tests for KIF

See if a view is displayed.

it(@"should be able to load a table view controller", ^{
     [tester waitForViewWithAccessibilityLabel:@"Locations Table"];
}

Tap a cell in a table view controller.

it(@"should be able to tap a cell in a table view controller", ^{
     NSIndexPath *thirdLocation = [NSIndexPath indexPathForRow:2 inSection:0];
     [tester tapRowAtIndexPath:thirdLocation inTableViewWithAccessibilityIdentifier:@"Locations Table"];
}

Tap a view (such as a button)

it(@"should be able to tap a cell in a table view controller", ^{
      [tester tapRowAtIndexPath:thirdLocation inTableViewWithAccessibilityIdentifier:@"Locations Table"];
}

Check text of a cell

it(@"should have the correct third element in the table view", ^{
       NSIndexPath *thirdLocation = [NSIndexPath indexPathForRow:2 inSection:0];
       UITableViewCell *cell = (UITableViewCell*)[tester waitForCellAtIndexPath:thirdLocation inTableViewWithAccessibilityIdentifier:@"Locations Table"];
       expect(cell.textLabel.text).to.equal(@"Statue Of Liberty");
       expect(cell.detailTextLabel.text).to.equal(@"1");
});

Accessibility Label

KIF is relying on the accessibility label to find the views on the app. The accessibility label is on the identity inspector or it can be set programmatically.

Accessibility Label
Accessibility Label. Make sure it’s enabled.

Programmatically (in viewDidLoad of a table view controller for example):

self.tableView.accessibilityIdentifier = @"Trivia Table";

Week 3 Mistakes and Lessons

Topics this week: TableViewControllers

Symptom: When I see an empty table view controller, this is usually the culprit. 

Mistake: Forgetting to initializing the array in a table view controller.

Lesson: always initialize the array.


Symptom: The dreaded Apple Mach-O Linker Error. Linker command failed with exit code 1. This error is super intimidating because it’s so cryptic. (What the heck does that mean anyway?)

Mistake: Turned out that I had imported a KIF test file “TableViewControllerSpec.m” into another TableViewControllerSpec.m and it was giving the errors:

duplicate symbol _OBJC_METACLASS_$_TableViewControllerSpec ...
duplicate symbol _OBJC_CLASS_$_TableViewControllerSpec ...

Lesson: Don’t quit when you hit error messages. Read the logs, it usually gets you close to the problem.


Symptom: Realizing that the full solution to a problem is not worth implementing and not having the previous version. 

Mistake: Deleting code when it’s a partial solution to go for a full solution.

Lesson: Commit code before trying an experimental solution.


Symptom: Tests fail sometimes but not all the time.

Mistake: Not resetting the test conditions after each or before each tests. Test do not go on in order in KIF.

Lesson: Check the starting condition each time before a test is run. Resetting the tester is cool.

Week 2- Tinder Clone. Breaking down into Objects. Setting up the PageViewController

I felt that I had enough knowledge to start building apps so I decided to copy some popular apps with interesting mechanisms. The idea is to do one every week or two, so that I can build a lot of these apps and learn how they are built.

Why build lots of apps?

Part of my inspiration has been Ira Glass’s notes on storytelling where he walks about beginners needing to do a great volume of work before they can make things that they think are good enough. I’m starting that journey now.

Why reverse engineer apps and not build original apps?

When artists learn to paint, they copy other artists to learn how something is done. In app making, I’m doing the same by analyzing and appreciating how other developers have made their apps.

Project 1 is Tinder.

The interesting mechanism here is the swipe left or right to like or dislike.

Step 1 Break down the app into views

Sketch of Tinder Settings Page
Sketch of Tinder Settings Page
Sketch of Tinder Match Page
Sketch of Tinder Match Page
Sketch of Tinder Messages Page
Sketch of Tinder Messages Page

For my first iteration, I really just want the settings and the match page to work. The messages would be part of the second iteration and moments would be part of the third.

Step 2. Take the views and make the data model

I started by translating the Settings and Match views into classes and properties, leaving the methods fluid for now.

There needs to be a Person class.

Person Class
Person Class

The Facebook properties would be imported with Facebook authentication and the rest would be set by the customer.

For simplicity, I have the location as a NSString now.

When I thought about how the custom initializer would work, I realized that I would probably need to split this class up into small modules with Discovery Properties, and Facebook Properties as classes.

I realized that I would also need a way to match people. It made more sense to make a Matcher class because I would want a central place to store data and review the results (whether people liked each other) to see how well my matching algorithm was working.

MatchMaker Class
MatchMaker Class

Naturally, the array of matches would need a match object, so I wrote a match class.

Match Class
Match Class

Step 3. Make the basic interface in storyboard

Tinder has four pages but I just wanted to mock up the Settings, Match and Messages pages.

Specifications:

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right.
  • Starts on the Match Page

1st Iteration

Test: I tried three UIViewControllers with buttons on navigation bars that went between the three pages.

  1. Select the storyboard or make one (File > New > File Cmd-N, iOS > User Interface > Storyboard)
  2. Add three View Controllers from the bottom of the Utilities column (right sidebar).
  3. Add a navigation bar to each of the three view controllers.
  4. Add bar button items to each screen as shown below.
  5. Add segues for the bar buttons to the adjacent screens.
1st Iteration of the Storyboard
1st Iteration of the Storyboard

Results: As I found out after running this, the transitions make the new screen pop up from the bottom like a modal window.

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right. 
  • Starts on the Match Page

2nd Iteration

Test: Use a navigation controller.

I have a navigation controller with the root controller as the Settings page with the Match page and the Messages Page linked to that.

2nd Iteration of the Storyboard
2nd Iteration of the Storyboard using a Navigation Controller

Results: The transitions are now horizontal between pages. It’s not smooth like Tinder’s touch gestures, but it’s close. The app doesn’t start on the match page though if I want the sliding to work in this order.

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right. 
  • Starts on the Match Page

3rd Iteration

Test: Make the Match page the root view controller and add a link back to the settings page.

3rd Iteration of the Storyboard
3rd Iteration of the Storyboard

Results: After clicking the settings button, the transition slid from right to left and the Settings pages looks just like the messages page. Not what I wanted.

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right. 
  • Starts on the Match Page

4th Iteration

Test: I’m guessing that the transition is either a customer segue or there’s something about gestures that I don’t know. I’m going to look into gestures between views.

As I looked through the gestures, I noticed the Page View Controller. After googling it and finding this AppCoda tutorial,  the scroll transition looks exactly like what Tinder uses.

5th Iteration

A Page View Controller is a container controller that you put other view controllers inside.

So, I’m going to need to make three classes of view controllers that show the Settings, Match and Messages pages. Then, I’ll put them inside the page view controller.

Looking through the PageViewDemo from the AppCoda tutorial, I see that they have one view controller that creates a pageViewController, provides the data for the pageViewController and sets an array of PageContentViewControllers in the pageViewController.

Sidebar: Navigation Bar vs. Navigation Item

When I was using the Navigation Controller, I needed to add navigation items to the Settings, Match and Message view controllers to be able to add a title to each slide.

When I deleted the Navigation Controller, I made the navigation bar disappear. While the navigation item was still there, it was useless without the navigation bar, so I had to add three navigation bars onto the Settings, Match and Message view controllers.

Where a navigation controller exists, add navigation items. Where there is not , add navigation bars. 

Test: In order to implement the pageViewController, I added a ViewController class inheriting from UIViewController and added a PageViewController, a SettingsViewController, MatchViewController, and MessagesViewController into the properties. The last three are custom classes inheriting from UIViewController.

ViewController.h inheriting from UIViewController
ViewController.m inheriting from UIViewController

I created the five view controllers in the Storyboard and added their class name to each corresponding storyboard ID.

5th iteration of the storyboard
5th iteration of the storyboard

Then I set the properties in ViewController.m to each of the storyboard IDs.

ViewController.m
ViewController.m

Here I set the match view controller to be the starting one.

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right. 
  • Starts on the Match Page

Now it starts with the match page, but doesn’t have any other other pages. The Settings and Messages pages are there, but they are not connected yet.

I see that the PageViewDemo has two methods called

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController

and

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController

That’s probably what I want to tell the page view controller how to switch between the Settings, Match and Messages view controllers.

When I try to type those in, they were not autocompleting, which means that I was missing something. I think it’s because I haven’t set the ViewController to follow the pageViewControllerDataSource Protocol. After I add that to the ViewController.h file. I get a warning. Yes!!

PageViewControllerDataSource Protocol Warning
PageViewControllerDataSource Protocol Warning

Now I understand why the PageViewDemo had

    self.pageViewController.dataSource = self;

in the viewDidLoad.

Since I only had three pages, I wrote two simple if statement for the viewControllerBefore and viewControllerAfter methods and everything works.

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right. 
  • Starts on the Match Page