Flatiron School Week 12 – iOS Full time Report

For notes on the first half of the program, check out the halftime report.

The End of the Beginning

These three months have been a tremendous experience. I recall other three-month periods where nearly nothing has changed. In stark contrast, I didn’t know anything about making apps three months ago and now I can make 80% of the good ones out there and I will soon be able to make the last 20%.

Amidst celebration, the feeling of the real world seeps in. The realities of finding a job are readily apparent. These three months have been a working vacation, a sabbatical, with no need to worry about worldly concern, just code. Yet, this search feels so much different from the post-college job hunt. That felt like gauntlet of hurdles, where the goal was to shape myself into the candidate that would be hired. This feels like a dating process. Maybe it’s always been that way, and I’ve only now grown mature enough to see it as such. I go into each interview, each conversation with as much thought about how much I like them as I do with how much they may like me.


Flatiron School Placements

This week, there’s been a lot of job advice, most of which I agree with wholeheartedly.

  • The placements team tell us how to prepare for the technical interview.
  • They tell us how to think about the long term (your career) not just your first job.
  • They tell us how to network.
  • They help us practice mock cultural and technical interviews.
  • They host talks to connect us with alumni and industry people.
  • They help us with our individual situations and questions.
  • All these things are meant to help us get a get a job.

Note: If you’re an international student, it may be hard to find a company to sponsor your work visa. 

Note 2: iOS developer make slightly more than Ruby on average.

I’m expecting the job finding process to go through a few iterations and take at least 2 months.

My advice: 1. Just like dating, you have to go out there, meet people and build relationships. 2. Keep coding to keep improving yourself. 3. Stop when you’ve found a place you’re excited to work for at least a year or two.


iOS Client Projects 

In interviews, people like to see that you’ve worked on projects before and especially if you’re worked with clients before.

One of the things that no one told me was that iOS students have client projects for the last four weeks. Ruby students work on their own projects.

I worked with a team for a client called HireCanvas, which makes the college campus recruiting process easier (how appropriate for my situation).  We helped them build out a iOS version of their web app.

Half of your time will be spend figuring out where to go

  • For the first eight weeks of labs, objectives and deadlines were established for us. Coding as a career gets real when I sit in front of a computer and spend half the time figuring what needs to be done when and what should be done next. Not having this structure makes me appreciate the time and thought put into designing a curriculum. On the project, we also have to make choices that will determine whether the project will be done in time. Do I use a cocoa pod or build my own?

Git only gets real in groups

Teamwork helps you figure out what makes good teammates

  • The other thing about working with people is that it helps you develop an intuition for who you would want to work with and who you wouldn’t. Sometimes people surprise you. You might think that an experienced programmer would be great to have on a team until you realize that commitment was a more important trait to look for.

Communication Communication Communication

  • As with any type of project, communication turned out to be key. When communication breaks down, people assume the worst and death spirals are started. Talking through it, you realize that the thing you were worried about was not that important to the other person.

A note on legal agreements

Read before you sign. Try to change what you don’t like.

Unlike software agreements, employment contract and non-disclosure agreements should be reason to pause. People will pressure you to sign, so you can get the job. They will tell you that it’s standard practice. That’s a whole lot of bs.

If they’re never going to use a part of the agreement, you shouldn’t have to agree to it.

Make sure you understand what you’re signing and if you don’t like it. Ask if you can change it. A lot of times it’s so boilerplate that the people you’re working with don’t even know what’s in it. If it can’t be changed, ask whether it’s worth taking on the work. I’d rather forgo a project than sign something I’ll regret later.

One thing the school could have done better was show us the legal agreements we would need to sign for the client projects and for attending the school before money and time was spent.


Overall Lessons

1. The number one goal for coming to Flatiron School is to learn how to be self-sufficient as a developer

Use the time to learn to fish and you will have skills for a lifetime. Use this time to try and fail, not produce a portfolio or kickstart your startup. The real world is not as forgiving.

2. Flatiron School brings together very nice, talented people. Build relationships with them.

For most of college, I didn’t appreciate the diversity of human talent and what I could learn from others. At Flatiron, the side projects I’ve seen really brings out people’s passions. I appreciate being around so many talented people (students especially).

3. Have enough living expenses for 6 months before you come to Flatiron

People who only had four months are now scrambling to find the first jobs they can. That’s an unenviable position to be in. Give yourself the time to think about what you want to do after Flatiron. You might be a different person by then.

4. Learning to code will make you feel more empowered than ever before

Not only can you make other people’s dreams come true, you can make your own reality.

Flatiron School Week 12 – Learn Swift or Objective-C?

I was asked whether a person with no iOS experience should learn Swift or Objective-C. After talking to a few people at the school, I’ve come to a simple conclusion.

Some Observations about Swift

In a couple days of playing with Swift, I’ve noticed that I could pick it up pretty easily. The frameworks are the same, but there’s some cool new syntax that will save a lot of typing (closures come to mind). I’ve also found that similar to learning Spanish and French at the same time, learning Swift will confuse your Objective-C syntax.

Learn Objective-C if you plan to be a professional iOS developer within the next year

For professional iOS developers, most companies are still in Objective-C and you will be expected to read and write it. Simple as that.

Learn Swift if you want to just work on your own side projects

Especially if you’re working on iOS development on the side, by the time you’re familiar enough with iOS frameworks, Swift will be more mature, more usable by then.

Flatiron School Presents – The Multipeer Connectivity Framework (Lattice)

Another Flatiron student and I were interested in the Multipeer Connectivity Framework in iOS, so we made a chat app called Lattice that uses multipeer in the event of a natural disaster as a demonstration of the technology.

I’m going to talk about how the basics of multipeer connectivity work.

The Multipeer Connectivity (MC) Framework

What does it do?

Multipeer allows devices to connect with others nearby without the internet.

How does it work?

Multipeer Connectivity is an implementation of mesh networking, creating a local network between devices within Wifi or Bluetooth range. Not only are new devices connected with devices within range but also with all devices that those devices are connected to, even if the destination device is not within range of the sender.

That’s the real power of mesh networks. If permitted, devices can send messages relayed through other devices.

How might this be useful?

  1. Mesh networks allow communication when internet infrastructure is damaged (such as natural disasters).
  2. Mesh networks allow communication when internet access is restricted by governments, seen in the protests of Hong Kong.
  3. Mesh networks allow communication in internet-less situation (camping trips, road trips, subway commutes, and foreign vacations).

Lattice – Demo Project

Before we dive into the nitty gritty, I’ve placed Lattice on Github, so you can see exactly how the snippets below are used.


Setting up the MCManager

We have a MCManager class, similar to an API manager or data store for Core Data. Four properties need to be set up.

MCPeerID

_peerID = [[MCPeerID alloc] initWithDisplayName:displayName];

This is the name of your device.

MCSession

_session = [[MCSession alloc] initWithPeer:self.peerID securityIdentity:nil encryptionPreference:MCEncryptionNone];
_session.delegate = self;

This is the session that your device will start. Think of a session like a chatroom.

MCNearbyServiceAdvertiser

_advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.peerID discoveryInfo:elements serviceType:LatticeServiceType];
self.advertiser.delegate = self;
[self.advertiser startAdvertisingPeer];

This adverser tells nearby browsers that it is looking to connect with a particular serviceType. DiscoveryInfo is a dictionary that the browser will receive when it finds this advertiser.

MCNearbyServiceBrowser

_browser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.peerID serviceType:LatticeServiceType];
self.browser.delegate = self;
[self.browser startBrowsingForPeers];

This browser looks for advertisers of a particular service type to connect to.


Connecting to Other Devices

Lattice, the demo app, will have an advertiser and browser running at the same time on each device.

Browser Finds Peer, Invites Peer

- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info
{
   [self.browser invitePeer:peerID toSession:self.session withContext:nil timeout:10]
}

When a browser finds an advertiser, it can parse the discoveryInfo that the advertiser set when it was created. In this case, we want the devices to connect automatically, so the browser sends an invitation to connect to the browser’s session.

Advertiser Accepts Invitation

- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void (^)(BOOL, MCSession *))invitationHandler
{ 
    invitationHandler(YES, self.session); 
}

Since we want the advertiser to connect automatically, the advertiser will reply YES to the invitation.

Session Confirms Connection

- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state

We can indeed confirm that the two devices are connected with this delegate method from the session. The session will tells us that the peer is now in the connected state.


Sending and Receiving Messages

Sending From the View Controller

[self.multipeerManager.session sendData:messageData toPeers:self.multipeerManager.session.connectedPeers withMode:MCSessionSendDataUnreliable error:&error];

Messages are sent from the session to the peers in the session.

Receiving In the MCManager

- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID
{
    //Kicks off a notification to the View Controller 
}

Messages are received by the session on the other devices. In Lattice, we triggered a notification with each message received.

Catching the Notification In the View Controller

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(peerDidReceiveDataWithNotification:) name:@"MCDidReceiveDataNotification" object:nil];
- (void)peerDidReceiveDataWithNotification:(NSNotification *)notification
{
    NSData *messageData = notification.userInfo[@"data"];
    NSArray *messages = [NSKeyedUnarchiver unarchiveObjectWithData:messageData];
    [self.demoData.messages addObject:messages[0]];
}

The notification was handled by the view controller to display the new message.


Limitations

iOS only

Multipeer Connectivity is a iOS framework.

Range can be limited on Bluetooth

We found it to be about 60 – 100 feet.

Downloading the app requires the internet?!

There should be a way for the app to spread virally with no internet.

Limited usefulness in zombie apocalypse

This would be great for finding survivors nearby without having to go room by room through scary dark buildings. During night when the zombies are outside, the bluetooth signal may not be long enough to communicate between buildings.


Special Thanks

Peter Fennema for showing how to connect via multipeer automatically

Jesse Squires for the messaging user interface: JSQMessagesViewController

Flatiron School Week 11 – Week in Review

Week 3 of Client Project

One of our teammates have been dealing with an emergency for most of the last two weeks, so we’re down to three people.

The Science Fair is Wednesday and we want to have something to showable, so we scoped down our project to just what needed to be shown.

From Monday to Tuesday, our team and our app really came together. We are in the performing stage of the team “form, storm, norm, perform” process. I’m glad that my investment of time paid off. Everyone pulled their own weight.

I’m really impressed why what we were able to do in 2 weeks. People were really impressed at the Science Fair.

I’ve been so focused that I haven’t really been paying attention to how the other client projects were going. It looks like they had bigger clients and bigger expectations. That’s the benefit of having a younger client.

We ended the week with a huge celebration over at Sammy’s. So much fun.

Flatiron School Week 10 – Week In Review

Week 2 of Client Projects

Notes to Self

This week felt like I was reliving my first year out of college when I was working as a paralegal/project manager in a client facing team. That year, I learned more about communication and relationship building than anything else, themes that came up this week.

  • Giving feedback is one of the most difficult conversations between people. Admitting that you’re wrong is another. All difficult conversations are best done promptly.
  • On a small team, having a team member that isn’t able to contribute at the same level is a productivity killer because it means that productive people need to stop and invest in that team member. (Sam Altman has more on this topic)
  • Communication is key. When communication breaks down, trust erodes and people assume the worst.
  • As much as possible, it’s good to talk directly with the people involved. You build trust this way.
  • Don’t hesitate to pick up the phone.
  • Teams should have a single point of contact when working with clients to provide updates.

Flatiron School Week 9 – Conquering Fear of Complex CocoaPods

For my Flatiron School Presents project, a classmate and I worked on a chat app.

For the chat user interface, I wanted it to look like Apple’s Messages app. There was a perfect CocoaPod for this: JSQMessagesViewController. After reading the Getting Started section, I still had no clue how to bridge the gap between my existing app and this UI. It was overwhelming and I was very discouraged.

9 Steps to Overcoming Fear of Complex CocoaPods

Step 1: Recognizing the problem

I knew that I was avoiding it. I looked for another CocoaPod but they weren’t quite the same. I even thought of building my own chat UI. I realized that I needed to be able to use CocoaPods though, so there was no getting around it.

Step 2: Read Zen Pencils

In need of inspiration, I turned to the Zen Pencils. I have the book too, which makes for very motivating reading.

Step 3: Go to sleep

It was 9 already and so I figured it was better to get some sleep than try that night.

Step 4: Try a simpler CocoaPod.

I hadn’t used enough CocoaPods up to this point, so I tried a smaller one. I looked at HPL chat. It had a tenth as many classes as JSQ did and I went for it. It didn’t work, but got me started.

Step 5: Look at the demo

The challenge of open source software is that there’s not always good documentation. It doesn’t cost money, but it costs time to read and understand how to use the code.

Fortunately, I saw that JSQ had a demo and downloaded it. Playing around with the demo really helped me understand how the pieces related to each other.

Step 6: Start customizing the demo

I didn’t need images or videos so, I got rid of the accessory button on the toolbar. I had to get familiar with the documentation to find which parts I needed to change. Once I removed the button, I felt much more confident about adapting the demo.

Step 7: Move the demo to a new project and make it work

Now I tried to make the demo fit my purpose, removing as much as I could to reduce it to the elements I needed. Here was a sandbox that I could use to unit test the functionality before I put it into my app and get everything tangled up.

Step 8. Add to the app

With the chat working on it’s own, I had to hook it up to my project. By this point, I realized that I just needed to make a couple  subclasses of the classes that the CocoaPod needed and replace my existing messages class with those.

Step 9. Fear Conquered

After I was done, I read the CocoaPod’s Getting Started again and now it all makes sense. Going through implementing this pod has been a process of gradual building of exposure, comfort and confidence with CocoaPods. I’m really happy that I did this. Fear of technology really comes down to not understanding how it works.

Flatiron School Week 9 – Resolving my first real git merge conflict

This week we started our client projects. 19 people broke down into teams of 4 or 5. There were four clients and our team had four people. This was the first time that I really needed to deal with git merge conflicts and develop a workflow.

It wasn’t long before I had a conflict.

There were three files that I conflicts with:

  1. AppDelegate.m
  2. the .pbxproj file
  3. a xccheckout file

1. AppDelegate.m

I knew how to resolve the first one, but as I tried to do that, my file directory on the left side of Xcode disappeared. It was time to ask for help. Zach, one of our TAs, helped me put together a plan to resolve the conflicts.

I had to back out of the merge, so I used

git merge --abort

and then reset the head to the last commit because there some uncommit changes that I didn’t want anymore.

git reset --hard <commit id>

2. the .pbxproj file

Apparently that file directory is stored in the .pbxproj file. The way that we resolved this was by telling git to just join the two versions together in the .gitattributes file by adding:

*.pbxproj merge=union

Another way to resolve this is to choose one version of the pbxproj.

git checkout --ours <directory/file>

Replace “ours” with “theirs” to get the version being merged into the current branch.

I had to add in some files that were no longer referenced by the pbxproj file after the merge using this second method.

3. a xccheckout file

For the .xccheckout file, this was a file that should have been ignored. I added this to the .gitignore file.

*.xccheckout

The problem was that both versions that I was trying to merge still had this file in tracked files. I had to remove this file from both repos by running:

git rm --cached <directory/file>

Once the .xccheckout file was removed, I could merge the two repos.

Both the .gitignore and .gitattributes files should be set up at the beginning of a project.

Update

Since that week, we’ve had to resolve conflicts many times. Following Github’s workflow has been pretty useful. I’ve found that the best way to resolve conflicts is prevention. We designed our workflow so that each person worked on a different part of the app and that minimized conflicts.

Flatiron School Week 8 – Week in Review

Topics: ScrollViews, views without storyboard, xibs/nibs, NSOperationQueue

This week was the last week of lecture, so we covered a grab bag of advanced topics.

Notes to Self

ScrollViews are sexy.

Bad public speakers (an anti-pattern)
  • don’t bring the audience with them and just start talking. Don’t explicitly say what questions they will address and how those are relevant to the audience.
    • Shouldn’t be thinking “what are we talking about?” “why are we talking about this?”
  • don’t speak loud enough.
  • don’t provide context for what they are saying and what problems they are trying to solve
  • go way too deep into things that the audience neither has context for or cares about
  • don’t paint a picture

Flatiron School Week 7 – Week in Review

Topics: APIs + Core Data, Authentication, Multithreading

Mistakes and Lessons

Symptom: API calls don’t tell you a lot when they don’t work. I wasn’t getting anything out of an internet request. Debugging API calls can feel like a text based adventure.

Mistake: Errors in strings are hard to debug. Apparently, when I had made the url, there was an extra “%”.

Lesson: Copy and paste urls that work from Postman, or use an encoder to convert strings to urls.

Symptom: When I changed the colors in my bocce game, the logic for choosing the next team to go broke.

Mistake: I had changed my colors away from the default colors (e.x. blueColor) to RGB values and had used == to compare the objects. The correct way to compare the objects is using an isEqual. Turns out that when you use [UIColor colorName] using == to compare is ok because they are at the same memory location.

Lesson: Use isEqual with objects and == with primitives.

Notes to Self

There are some times when I just have to slow down and plan out how something works. I was making a tableView that updated an API and a Core Data store. This very quickly got too complicated to keep in memory and I had to write down the interactions step by stem.

My partner and I decided to divide and conquer a lab. It became very hard for me to test along the way, because I was dependent on his piece to make mine work.

“If You Want To Go Fast, Go Alone. If You Want To Go Far, Go Together”

Flatiron School Week 7 – BocceGame built with UIDynamicAnimator

UIKit Dynamics

In my spare time, I wanted to look at animating physics collisions so I found UI Dynamics. It’s a part of UIKit that has some build-in functionality to animate physics based interactions. It’s generally not used for games, but rather for transitions and cool UI tricks.

A bocce game is a simple way to demonstrate this functionality. In bocce, there’s a target ball called the jack and two teams try to get their ball as close to the jack as possible. The team gets points for the number of balls that are closer to the jack than the nearest opponent ball.

Bocce Game Demo

Github Repo

How to use UIDynamicAnimator

You have an animator.

Animators can add behaviors.

Behaviors can add items that they affect.

My animator

    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

One behavior is the UIDynamicItemBehavior behavior that allows the ball to continue moving after released by the hand with the add

self.linearVelocity = [[UIDynamicItemBehavior alloc] init];
[self.linearVelocity addLinearVelocity:velocity forItem:gesture.view];
[self.animator addBehavior:self.linearVelocity];

Another behavior is the collision behavior that collides with either a wall or another item.

//initialize collision behavior
self.collision = [[UICollisionBehavior alloc] init];
[self.animator addBehavior:self.collision];

How to make this game

Dragging a ball

Set up a UIPanGestureRecognizer that calls the dragged: method.

    UIPanGestureRecognizer *draggable = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragged:)];
    [self.currentBlock addGestureRecognizer:draggable];

The dragged method has the form:

- (void) dragged:(UIPanGestureRecognizer *)gesture
{
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
//save the starting location
break;

case UIGestureRecognizerStateChanged:
{
CGPoint translation = [gesture translationInView:self.view];
 
//calculate the new location with the starting location and the translation
//set the location to the new location
 
break;
}
 
case UIGestureRecognizerStateEnded:
{
 
//figure out the velocity when the ball is released and add it to the item with the UIDynamicItemBehavior
}
 
break;
}
 
default:
break;
}

}
}

Continue a ball moving after release

Right now, the ball would stop when I let go of it. To keep it going at the same speed, get the velocity in view from the gestureRecognizer and add it to the UIDynamicItemBehavior with addLinearVelocity: forItem:

//Inside the case UIGestureRecognizerStateEnded
CGPoint velocity = [gesture velocityInView:self.view];
[self.linearVelocity addLinearVelocity:velocity forItem:gesture.view];
[self.animator addBehavior:self.linearVelocity];
 
//necessary to make the motion smooth, otherwise it will be jumpy if you hold down the 
[self.animator updateItemUsingCurrentState:gesture.view];

Stopping the ball

The ball will keep going on for a long time, so I needed to stop it somehow.

Stop the ball by adding friction. In UIDynamicItemBehavior, friction is the resistive force between two objects. I wanted resistance to simulate air resistance. Angular resistance was needed to stop the balls from spinning too.

self.linearVelocity.resistance = 8;
self.linearVelocity.angularResistance = 8;

Only allow one animation to run at a time

I found that trying to flick a ball before another had finished moving was making a really jumpy animation, so checked if the animation is running with each gesture recognized and only let the new block move after the other was finished moving.

- (void) dragged:(UIPanGestureRecognizer *)gesture
{
    if (![self.animator isRunning]) {
        switch (gesture.state) {
...

Detecting when the ball was stopped

To find out when to make the next block, I needed to know when the ball had stopped moving. I used the

- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator

method. It’s a delegate method so I had to set the

    self.animator.delegate = self;

Making the target ball bounce off the walls

I didn’t want the target ball to go off the screen, so I set the screen boundaries as walls,

[self.collision setTranslatesReferenceBoundsIntoBoundary:YES];

made the ball bound off all objects including boundaries (referenceBounds being those of self.view because the animator has self.view as the reference view),

[self.collision setCollisionMode:UICollisionBehaviorModeEverything];

and added another invisible wall, so that the ball would not end up on the top edge of the screen.

[self.collision addBoundaryWithIdentifier:@"end" fromPoint:CGPointMake(0, self.view.frame.size.height*0.1) toPoint:CGPointMake(self.view.frame.size.width, self.view.frame.size.height*0.1)];

Take the target ball out of collision

I wanted to make the target ball immovable, so I just had to remove it as an item of the collision.

[self.collision removeItem:self.currentBlock];

I also wanted future object to be able to go off the screen, so I removed the “end” boundary and the window boundaries.

[self.collision removeBoundaryWithIdentifier:@"end"];
[self.collision setTranslatesReferenceBoundsIntoBoundary:NO];

Ignoring most the call to dynamicAnimatorDidPause after a ball is made

After a new object is added, dynamicAnimatorDidPause is called, so I had to add a BOOL property justMadeBlock to ignore those calls.

Stop the moving block when it goes offscreen

If a ball was flicked too hard, then it would go off the screen and I would have to wait for it to stop before the next ball would load. To shorten that time, I made another boundary outside the edge of the screen and overwrote a delegate method to make the ball stop moving and not able to collide with other blocks.

[self.collision addBoundaryWithIdentifier:@"outOfBounds" forPath:[UIBezierPath bezierPathWithRect:CGRectMake(-90, -90, self.view.frame.size.width+180, self.view.frame.size.height+180)]];

Delegate method for the UICollisionBehavor

self.collision.collisionDelegate = self;
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p
{
    if ([((NSString *)identifier) isEqualToString:@"outOfBounds"]) {
        //not allow it to move
        [self.linearVelocity removeItem:item];
        //not allow it to interact with future blocks
        [self.collision removeItem:item];
    }
}

Calculate the nearest ball and marking the nearest ones

Once a colored ball had been thrown, there is now a winning ball (closest to the target).

I took all the balls on screen.

    NSArray *blocksOnScreen = [self.collision items];

Got their locations

Sorted the array of balls by location

And found the balls of the same color that were closest to the target ball

With those “winning balls”, I added a CALayer to them to mark them as winning.

The next to go would be the team further away from the target.

At the end of the game, a UIAlertController asks if you want to play again.

Adding indicators for the number of balls remaining per team

I used CAShapeLayers and added them to the self.view.layer. They would update as balls were made.

Adding a flashing turnIndicator

The alpha controls the opacity of the text label, so setAlpha:1.0 means make it appear.

UIViewAnimationOptionAutoreverse makes it disappear again.
UIViewAnimationOptionRepeat makes the animation keep going until you stop it.
UIViewAnimationOptionAllowUserInteraction allows you to remove the animation later on.

[UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionAutoreverse| UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction  animations:^{
        [self.turnIndicator setAlpha:1.0];
    } completion:nil];

Stopping animations

When I tried to reset the game, setting all the properties back to nil worked well, except for stopping the turnIndicator animation.

There I found that the animation is in the layer of the animated object.

[self.turnIndicator.layer removeAllAnimations];

Choosing a more attractive color scheme

Adobe’s color wheel was really cool. I like pastel colors, so this is what I chose.

Notes to Self

Writing code takes way longer than expected

I didn’t think that it would take very long to make this, (just slap some blocks on a screen right?) but building features takes time and focus. Really quickly, seeing jumpy animations makes you want to fix them and that’s the 20% that takes the most time. Writing blogs about code also takes hours…

User testing is super important to do throughout

I knew how the game was played, but it wasn’t obvious to people I showed it to. I really wouldn’t have known if I hadn’t had someone use the game.

Don’t let sidetracked on super advanced features when the basics are not done

The flip side of asking for feedback is that people start giving you really good suggestions, but I think you have to finish one phase before hacking away at the next thing.

Hacking is fun with something quick, but it’s no fun with a big project

This is the second project that I’ve hacked to together. It’s been fun, but I really would have liked to have classes for the data model. This game was made over three weekends and I was finding that when I wanted to add new features, I had to recall the mental model that I had for the project. Instead I should have had a game class that remembered the model for me.