Stanford CS193p: Developing Applications For iOS Fall 2013-14
Stanford CS193p: Developing Applications For iOS Fall 2013-14
Today
Introduction to Objective-C (cont) Xcode 5 Demonstration
Start building the simple Card Game Continue showing Card Game Model with Deck, PlayingCard, PlayingCardDeck
Card.h
#import <Foundation/Foundation.h>
Objective-C
#import "Card.h" @interface Card() @end @implementation Card
Card.m
@interface Card : NSObject @property (strong, nonatomic) NSString *contents; @property (nonatomic, getter=isChosen) BOOL chosen; @property (nonatomic, getter=isMatched) BOOL matched; - (int)match:(NSArray *)otherCards;
- (int)match:(NSArray *)otherCards { int score = 0; for (Card *card in otherCards) { if ([card.contents isEqualToString:self.contents]) { score = 1; } } return score; }
@end
@end
Deck.h
#import <Foundation/Foundation.h> @interface Deck : NSObject
Objective-C
#import "Deck.h" @interface Deck() @end @implementation Deck
Deck.m
@end
@end
Deck.h
#import <Foundation/Foundation.h> @interface Deck : NSObject
Objective-C
#import "Deck.h" @interface Deck() @end @implementation Deck
Deck.m
Note that this method has 2 arguments (and returns nothing). Its called addCard:atTop:. And this one takes no arguments and returns a Card (i.e. a pointer to an instance of a Card in the heap).
@end
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @interface Deck() @end
Deck.m
We must #import the header le for any class we use in this le (e.g. Card).
@end
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @interface Deck() @end @implementation Deck
Deck.m
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h"
Deck.m
Arguments to methods @interface Deck() (like the atTop: argument) @end are never optional.
@implementation Deck
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h"
Deck.m
Arguments to methods @interface Deck() (like the atTop: argument) @end are never optional.
@implementation Deck
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h"
Deck.m
Arguments to methods @interface Deck() (like the atTop: argument) @end are never optional.
@implementation Deck
} - (void)addCard:(Card *)card { [self addCard:card atTop:NO]; } - (Card *)drawRandomCard { } @end Stanford CS193p Fall 2013
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @interface Deck() @end @implementation Deck
Deck.m
A deck of cards obviously needs some storage to keep the cards in. We need an @property for that. But we dont want it to be public (since its part of our private, internal implementation).
} - (void)addCard:(Card *)card { [self addCard:card atTop:NO]; } - (Card *)drawRandomCard { } @end Stanford CS193p Fall 2013
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck
Deck.m
A deck of cards obviously needs some storage to keep the cards in. We need an @property for that. But we dont want it to be public (since its part of our private, internal implementation).
} - (void)addCard:(Card *)card { [self addCard:card atTop:NO]; } - (Card *)drawRandomCard { } @end Stanford CS193p Fall 2013
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck
Deck.m
Now that we have a property to store our cards in, lets take a look at a sample implementation of the addCard:atTop: method.
- (void)addCard:(Card *)card atTop:(BOOL)atTop { if (atTop) { [self.cards insertObject:card atIndex:0]; } else { [self.cards addObject:card]; } }
- (void)addCard:(Card *)card { [self addCard:card atTop:NO]; ... and these are NSMutableArray } - (Card *)drawRandomCard { } @end
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck
Deck.m
But theres a problem here. When does the object pointed to by the pointer returned by self.cards ever get created?
- (void)addCard:(Card *)card atTop:(BOOL)atTop { if (atTop) { [self.cards insertObject:card atIndex:0]; } else { [self.cards addObject:card]; } Declaring a @property } - (void)addCard:(Card *)card { [self addCard:card atTop:NO]; } - (Card *)drawRandomCard { } @end
makes space in the instance for the pointer itself, but not does not allocate space in the heap for the object the pointer points to.
Stanford CS193p Fall 2013
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck - (NSMutableArray *)cards { return _cards; } - (void)addCard:(Card *)card atTop:(BOOL)atTop { if (atTop) { [self.cards insertObject:card atIndex:0]; } else { [self.cards addObject:card]; } } - (void)addCard:(Card *)card { [self addCard:card atTop:NO]; } - (Card *)drawRandomCard { } @end
Deck.m
The place to put this needed heap allocation is in the getter for the cards @property.
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck - (NSMutableArray *)cards { if (!_cards) _cards = [[NSMutableArray alloc] init]; return _cards; }
Deck.m
The place to put this needed heap allocation is in the getter for the cards @property.
- (void)addCard:(Card *)card atTop:(BOOL)atTop { if (atTop) { All properties start out with a value of 0 [self.cards insertObject:card atIndex:0]; (called nil for pointers to objects). } else { So all we need to do is allocate and initialize the object [self.cards addObject:card]; the pointer to it is nil. } } This is called lazy instantiation. - (void)addCard:(Card *)card { [self addCard:card atTop:NO]; } - (Card *)drawRandomCard { } @end
if
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck
Deck.m
- (NSMutableArray *)cards { if (!_cards) _cards = [[NSMutableArray alloc] init]; return _cards; } - (void)addCard:(Card *)card atTop:(BOOL)atTop { if (atTop) { [self.cards insertObject:card atIndex:0]; } else { [self.cards addObject:card]; } } - (void)addCard:(Card *)card { [self addCard:card atTop:NO]; } - (Card *)drawRandomCard { } @end
Well talk about allocating and initializing objects more later, but heres a simple way to do it.
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck - (NSMutableArray *)cards { if (!_cards) _cards = [[NSMutableArray alloc] init]; return _cards; } - (void)addCard:(Card *)card atTop:(BOOL)atTop { if (atTop) { [self.cards insertObject:card atIndex:0]; } else { [self.cards addObject:card]; } } - (void)addCard:(Card *)card { [self addCard:card atTop:NO]; } - (Card *)drawRandomCard { } @end
Deck.m
Now the cards property will always at least be an empty mutable array, so this code will always do what we want.
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck - (NSMutableArray *)cards { if (!_cards) _cards = [[NSMutableArray alloc] init]; return _cards; } - (void)addCard:(Card *)card atTop:(BOOL)atTop { - (void)addCard:(Card *)card { } - (Card *)drawRandomCard { }
Deck.m
Lets collapse the code weve written so far to make some space.
} @end
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck - (NSMutableArray *)cards { if (!_cards) _cards = [[NSMutableArray alloc] init]; return _cards; } - (void)addCard:(Card *)card atTop:(BOOL)atTop { - (void)addCard:(Card *)card { } - (Card *)drawRandomCard { Card *randomCard = nil; }
Deck.m
drawRandomCard simply grabs a card from a random spot in our self.cards array.
return randomCard; } @end Stanford CS193p Fall 2013
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck - (NSMutableArray *)cards { if (!_cards) _cards = [[NSMutableArray alloc] init]; return _cards; } - (void)addCard:(Card *)card atTop:(BOOL)atTop { - (void)addCard:(Card *)card { } - (Card *)drawRandomCard { Card returns *randomCard nil; arc4random() a random = integer. }
Deck.m
unsigned index = arc4random() % [self.cards count]; randomCard = self.cards[index]; [self.cards removeObjectAtIndex:index]; return randomCard; } @end
These square brackets actually are the equivalent of sending the message objectAtIndexedSubscript: to the array. Stanford CS193p
Fall 2013
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck - (NSMutableArray *)cards { if (!_cards) _cards = [[NSMutableArray alloc] init]; return _cards; } - (void)addCard:(Card *)card atTop:(BOOL)atTop { - (void)addCard:(Card *)card { } - (Card *)drawRandomCard { Card *randomCard = nil; }
Deck.m
Calling objectAtIndexedSubscript: with an argument of zero on an empty array will crash (array index out of bounds)! So lets protect against that case.
} @end
if ([self.cards count]) { unsigned index = arc4random() % [self.cards count]; randomCard = self.cards[index]; [self.cards removeObjectAtIndex:index]; } return randomCard; Stanford CS193p Fall 2013
Deck.h
#import <Foundation/Foundation.h> #import "Card.h" @interface Deck : NSObject
Objective-C
#import "Deck.h" @implementation Deck - (NSMutableArray *)cards { if (!_cards) _cards = [[NSMutableArray alloc] init]; return _cards; } - (void)addCard:(Card *)card atTop:(BOOL)atTop { - (void)addCard:(Card *)card { } - (Card *)drawRandomCard { Card *randomCard = nil; if ([self.cards count]) { unsigned index = arc4random() % [self.cards count]; randomCard = self.cards[index]; [self.cards removeObjectAtIndex:index]; } return randomCard; } @end }
Deck.m
PlayingCard.h
Objective-C
PlayingCard.m
Lets see what its like to make a subclass of one of our own classes. In this example, a subclass of Card specic to a playing card (e.g. A).
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
Of course we must #import our superclass. And #import our own header le in our implementation le.
@end
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
@end
A PlayingCard has some properties that a vanilla Card doesnt have. Namely, the PlayingCards suit and rank.
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
Well represent the suit as an NSString that simply contains a single character corresponding to the suit (i.e. one of these characters: ). If this property is nil, itll mean suit not set. Well represent the rank as an integer from 0 (rank not set) to 13 (a King).
@end
NSUInteger
is a typedef for an unsigned integer. We could just use the C type unsigned int here. Its mostly a style choice. Many people like to use NSUInteger and NSInteger in public API and unsigned int and int inside implementation. But be careful, int is 32 bits, NSInteger might be 64 bits. If you have an NSInteger that is really big (i.e. > 32 bits worth) it could get truncated if you assign it to an int. Probably safer to use one or the other everywhere.
Stanford CS193p Fall 2013
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard - (NSString *)contents {
PlayingCard.m
Users of our PlayingCard class might well simply access suit and rank properties directly. But we can also support our superclasss contents property by overriding the getter to return a suitable (no pun intended) NSString.
Even though we are overriding the implementation of the contents method, we are not re-declaring the contents property in our header le. Well just inherit that declaration from our superclass.
Stanford CS193p Fall 2013
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard - (NSString *)contents { }
PlayingCard.m
The method stringWithFormat: is an NSString method thats sort of like using the C function printf to create the string.
Users of our PlayingCard class might well simply access suit and rank properties directly. But we can also support our superclasss contents property by overriding the getter to return a suitable (no pun intended) NSString.
Note we are creating an NSString here in a different way than alloc/init. Well see more about class methods like stringWithFormat: a little later.
Even though we are overriding the implementation of the contents method, we are not re-declaring the contents property in our header le. Well just inherit that declaration from our superclass.
Stanford CS193p Fall 2013
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard - (NSString *)contents { }
PlayingCard.m
Calling the getters of our two properties (rank and suit) on ourself.
But this is a pretty bad representation of the card (e.g., it would say 11 instead of J and 1 instead of A).
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
@end
Well create an NSArray of NSStrings, each of which corresponds to a given rank. Again, 0 will be rank not set (so well use ?). 11, 12 and 13 will be J Q K and 1 will be A. Then well create our J string by appending (with the stringByAppendingString: method) the suit onto the end of the string we get by looking in the array.
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
Notice the @[ ] notation to create an array.
@end
Heres the array-accessing [] notation again (like we used with self.cards[index] earlier).
All of these notations are converted into normal message-sends by the compiler. For example, @[ ... ] is [[NSArray alloc] initWithObjects:...]. rankStrings[self.rank] is [rankStrings objectAtIndexedSubscript:self.rank].
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
@end
This is nice because a not yet set rank shows up as ?. But what about a not yet set suit? Lets override the getter for suit to make a suit of nil return ?.
Yet another nice use for properties versus direct instance variables.
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
@end
Lets take this a little further and override the setter for suit to have it check to be sure no one tries to set a suit to something invalid.
- (void)setSuit:(NSString *)suit { if ([@[@"!",@""",@"#",@"$"] containsObject:suit]) { _suit = suit; } } - (NSString *)suit { return _suit ? _suit : @"?"; } @end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
@end
Notice that we can embed the array creation as the target of this message send. Were simply sending containsObject: to the array created by the @[ ].
- (void)setSuit:(NSString *)suit { if ([@[@"!",@""",@"#",@"$"] containsObject:suit]) { _suit = suit; } } - (NSString *)suit { return _suit ? _suit : @"?"; } @end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
@end
But theres a problem here now. A compiler warning will be generated if we do this. Why? Because if you implement BOTH the setter and the getter for a property, then you have to create the instance variable for the property yourself.
- (void)setSuit:(NSString *)suit { if ([@[@"!",@""",@"#",@"$"] containsObject:suit]) { _suit = suit; } } - (NSString *)suit { return _suit ? _suit : @"?"; } @end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
@end
But theres a problem here now. A compiler warning will be generated if we do this. Why? Because if you implement BOTH the setter and the getter for a property, then you have to create the instance variable for the property yourself.
- (NSString *)contents { NSArray *rankStrings = @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter
Luckily, the compiler can help with this using the @synthesize directive.
If you implement only the setter OR the getter (or neither), the compiler adds this @synthesize for you.
- (void)setSuit:(NSString *)suit { if ([@[@"!",@""",@"#",@"$"] containsObject:suit]) { _suit = suit; } } - (NSString *)suit { return _suit ? _suit : @"?"; } @end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter
@end
We almost always pick an instance variable name that is underbar followed by the name of the property.
- (void)setSuit:(NSString *)suit { if ([@[@"!",@""",@"#",@"$"] containsObject:suit]) { _suit = suit; } } - (NSString *)suit { return _suit ? _suit : @"?"; } @end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter
@end
You should only ever access the instance variable directly ...
- (void)setSuit:(NSString *)suit ... in the propertys { if ([@[@"!",@""",@"#",@"$"] containsObject:suit]) { _suit = suit; } } ... in its getter ... - (NSString *)suit { return _suit ? _suit : @"?"; } @end
setter ...
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter
@end
All of the methods weve seen so far are instance methods. They are methods sent to instances of a class. But it is also possible to create methods that are sent to the class itself. Usually these are either creation methods (like alloc or stringWithFormat:) or utility methods.
- (void)setSuit:(NSString *)suit { if ([@[@"!",@""",@"#",@"$"] containsObject:suit]) { _suit = suit; } } - (NSString *)suit { return _suit ? _suit : @"?"; } @end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter + (NSArray *)validSuits { return }
Heres an example of a class utility method which returns an NSArray of the NSStrings which are valid suits (e.g. , , , and ).
Since a class method is not sent to an instance, we cannot reference our properties in here (since properties represent per-instance storage).
- (void)setSuit:(NSString *)suit { if ([@[@"!",@""",@"#",@"$"] containsObject:suit]) { _suit = suit; } } - (NSString *)suit { return _suit ? _suit : @"?"; } @end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter + (NSArray *)validSuits { return @[@"!",@""",@"#",@"$"]; } - (void)setSuit:(NSString *)suit { if ([ _suit = suit; } } - (NSString *)suit { return _suit ? _suit : @"?"; } @end
@end
Heres an example of a class utility method which returns an NSArray of the NSStrings which are valid suits (e.g. , , , and ).
We actually already have the array of valid suits, so lets just move that up into our new class method.
containsObject:suit]) {
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter + (NSArray *)validSuits { return @[@"!",@""",@"#",@"$"]; }
@end
See how the name of the class appears in the place youd normally see a pointer to an instance of an object?
- (void)setSuit:(NSString *)suit { if ([[PlayingCard validSuits] containsObject:suit]) { ([ _suit = suit; } } - (NSString *)suit { return _suit ? _suit : @"?"; } @end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank;
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter + (NSArray *)validSuits { return @[@"!",@""",@"#",@"$"]; }
@end
See how the name of the class appears in the place youd normally see a pointer to an instance of an object?
- (void)setSuit:(NSString *)suit { if ([[PlayingCard validSuits] containsObject:suit]) { ([ _suit = suit; } } Itd probably be instructive to go back and look at the invocation of - (NSString *)suit Also, make sure you understand that stringByAppendingString: above { is not a class method, it is an instance method. return _suit ? _suit : @"?"; } Stanford CS193p @end Fall 2013
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank; + (NSArray *)validSuits; @end
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter
The validSuits class method might be useful to users of our PlayingCard class, so lets make it public.
+ (NSArray *)validSuits { return @[@"!",@""",@"#",@"$"]; } - (void)setSuit:(NSString *)suit { if ([[PlayingCard validSuits] containsObject:suit]) { _suit = suit; } } - (NSString *)suit { return _suit ? _suit : @"?"; } @end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank; + (NSArray *)validSuits; @end
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter + (NSArray *)validSuits { } - (void)setSuit:(NSString *)suit { } - (NSString *)suit { }
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank; + (NSArray *)validSuits; @end
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter + (NSArray *)validSuits { } - (void)setSuit:(NSString *)suit { } - (NSString *)suit { }
Lets move our other array (the strings of the ranks) into a class method too.
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank; + (NSArray *)validSuits; @end
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
And now lets call that class method.
- (NSString *)contents { NSArray *rankStrings = [PlayingCard rankStrings]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter + (NSArray *)validSuits { } Note that we are not - (void)setSuit:(NSString *)suit { } - (NSString *)suit { } required to declare this earlier + (NSArray *)rankStrings { return @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; }
Well leave this one private because the public API for the rank is purely numeric.
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank; + (NSArray *)validSuits; + (NSUInteger)maxRank; @end
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = [PlayingCard rankStrings]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter + (NSArray *)validSuits { } - (void)setSuit:(NSString *)suit { } - (NSString *)suit { } + (NSArray *)rankStrings { return @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; } + (NSUInteger)maxRank { return [[self rankStrings] count]-1; }
But heres another class method that might be good to make public. So well add it to the header le.
@end
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank; + (NSArray *)validSuits; + (NSUInteger)maxRank; @end
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = [PlayingCard rankStrings]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter + (NSArray *)validSuits { } - (void)setSuit:(NSString *)suit { } - (NSString *)suit { } + (NSArray *)rankStrings { return @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; } + (NSUInteger)maxRank { return [[self rankStrings] count]-1; }
And, nally, lets use maxRank inside the setter for the rank @property to make sure the rank is never set to an improper value.
PlayingCard.h
#import "Card.h" @interface PlayingCard : Card @property (strong, nonatomic) NSString *suit; @property (nonatomic) NSUInteger rank; + (NSArray *)validSuits; + (NSUInteger)maxRank; @end
Objective-C
#import "PlayingCard.h" @implementation PlayingCard
PlayingCard.m
- (NSString *)contents { NSArray *rankStrings = [PlayingCard rankStrings]; return [rankStrings[self.rank] stringByAppendingString:self.suit]; } @synthesize suit = _suit; // because we provide setter AND getter + (NSArray *)validSuits { } - (void)setSuit:(NSString *)suit { } - (NSString *)suit { }
Thats it for our PlayingCard. Its a good example of array notation, @synthesize, class methods, and using getters and setters for validation.
+ (NSArray *)rankStrings { return @[@"?",@"A",@"2",@"3",...,@"10",@"J",@"Q",@"K"]; } + (NSUInteger)maxRank { return [[self rankStrings] count]-1; } - (void)setRank:(NSUInteger)rank { if (rank <= [PlayingCard maxRank]) { _rank = rank; } } @end
PlayingCardDeck.h
#import "Deck.h" @interface PlayingCardDeck : Deck
Objective-C
#import "PlayingCardDeck.h" @implementation PlayingCardDeck
PlayingCardDeck.m
@end
Lets look at one last class. This one is a subclass of Deck and represents a full 52-card deck of PlayingCards.
@end
PlayingCardDeck.h
#import "Deck.h" @interface PlayingCardDeck : Deck
Objective-C
#import "PlayingCardDeck.h" @implementation PlayingCardDeck
PlayingCardDeck.m
@end
It appears to have no public API, but it is going to override a method that Deck inherits from NSObject called init.
init will contain everything
@end
PlayingCardDeck.h
#import "Deck.h" @interface PlayingCardDeck : Deck
Objective-C
#import "PlayingCardDeck.h" @implementation PlayingCardDeck - (instancetype)init {
PlayingCardDeck.m
@end
or NSMutableArray *cards = [[NSMutableArray alloc] init] Classes can have more complicated initializers than just plain init (e.g. initWithCapacity: or some such). Well talk more about that next week as well.
} @end
Initialization in Objective-C happens immediately after allocation. We always nest a call to init around a call to alloc. e.g. Deck *myDeck = [[PlayingCardDeck alloc] init]
Only call an init method immediately after calling alloc to make space in the heap for that new object. And never call alloc without immediately calling some init method on the newly allocated object.
Stanford CS193p Fall 2013
PlayingCardDeck.h
#import "Deck.h" @interface PlayingCardDeck : Deck
Objective-C
#import "PlayingCardDeck.h" @implementation PlayingCardDeck - (instancetype)init {
PlayingCardDeck.m
@end
Notice this weird return type of instancetype. It basically tells the compiler that this method returns an object which will be the same type as the object that this message was sent to. We will pretty much only use it for init methods. Dont worry about it too much for now. But always use this return type for your init methods.
} @end
PlayingCardDeck.h
#import "Deck.h" @interface PlayingCardDeck : Deck
Objective-C
#import "PlayingCardDeck.h" @implementation PlayingCardDeck - (instancetype)init { self = [super init]; if (self) {
PlayingCardDeck.m
@end
This sequence of code might also seem weird. Especially an assignment to self! This is the ONLY time you would ever assign something to self. The idea here is to return nil if you cannot initialize this object. But we have to check to see if our superclass can initialize itself. The assignment to self is a bit of protection against our trying to continue to initialize ourselves if our superclass couldnt initialize. Just always do this and dont worry about it too much.
PlayingCardDeck.h
#import "Deck.h" @interface PlayingCardDeck : Deck
Objective-C
#import "PlayingCardDeck.h" @implementation PlayingCardDeck - (instancetype)init { self = [super init]; if (self) {
PlayingCardDeck.m
Sending a message to super is how we send a message to ourselves, but use our superclasss implementation instead of our own. Standard object-oriented stuff.
@end
PlayingCardDeck.h
#import "Deck.h" @interface PlayingCardDeck : Deck
Objective-C
#import "PlayingCardDeck.h" @implementation PlayingCardDeck - (instancetype)init { self = [super init];
PlayingCardDeck.m
@end
The implementation of init is quite simple. Well just iterate through all the suits and then through all the ranks in that suit ...
if (self) { for (NSString *suit in [PlayingCard validSuits]) { for (NSUInteger rank = 1; rank <= [PlayingCard maxRank]; rank++) {
} } } @end }
return self;
PlayingCardDeck.h
#import "Deck.h" @interface PlayingCardDeck : Deck
Objective-C
#import "PlayingCardDeck.h" @implementation PlayingCardDeck - (instancetype)init { self = [super init]; if (self) { for (NSString *suit for (NSUInteger PlayingCard card.rank = card.suit = } } } @end }
PlayingCardDeck.m
@end
Then we will allocate and initialize a PlayingCard and then set its suit and rank.
in [PlayingCard validSuits]) { rank = 1; rank <= [PlayingCard maxRank]; rank++) { *card = [[PlayingCard alloc] init]; rank; suit;
return self;
We never implemented an init method in PlayingCard, so it just inherits the one from NSObject. Even so, we must always call an init method after alloc.
Stanford CS193p Fall 2013
PlayingCardDeck.h
#import "Deck.h" @interface PlayingCardDeck : Deck
Objective-C
#import "PlayingCardDeck.h" #import "PlayingCard.h" @implementation PlayingCardDeck - (instancetype)init { self = [super init]; if (self) { for (NSString *suit for (NSUInteger PlayingCard card.rank = card.suit = } } } @end }
PlayingCardDeck.m
We will need to #import PlayingCards header le since we are referencing it now in our implementation.
@end
Then we will allocate and initialize a PlayingCard and then set its suit and rank.
in [PlayingCard validSuits]) { rank = 1; rank <= [PlayingCard maxRank]; rank++) { *card = [[PlayingCard alloc] init]; rank; suit;
return self;
We never implemented an init method in PlayingCard, so it just inherits the one from NSObject. Even so, we must always call an init method after alloc.
Stanford CS193p Fall 2013
PlayingCardDeck.h
#import "Deck.h" @interface PlayingCardDeck : Deck
Objective-C
#import "PlayingCardDeck.h" #import "PlayingCard.h" @implementation PlayingCardDeck - (instancetype)init { self = [super init];
PlayingCardDeck.m
@end
Finally we just add each PlayingCard we create to ourself (we are a Deck, remember).
if (self) { for (NSString *suit in [PlayingCard validSuits]) { for (NSUInteger rank = 1; rank <= [PlayingCard maxRank]; rank++) { PlayingCard *card = [[PlayingCard alloc] init]; card.rank = rank; card.suit = suit; [self addCard:card]; } } } return self; } @end
PlayingCardDeck.h
#import "Deck.h" @interface PlayingCardDeck : Deck
Objective-C
#import "PlayingCardDeck.h" #import "PlayingCard.h" @implementation PlayingCardDeck - (instancetype)init { self = [super init];
PlayingCardDeck.m
@end
if (self) { for (NSString *suit in [PlayingCard validSuits]) { for (NSUInteger rank = 1; rank <= [PlayingCard maxRank]; rank++) { PlayingCard *card = [[PlayingCard alloc] init]; card.rank = rank; card.suit = suit; [self addCard:card]; } } } return self; } @end
And thats it! We inherit everything else we need to be a Deck of cards (like the ability to drawRandomCard) from our superclass.
Demo
Lets start building a Card Game out of these classes
Today well just have a single card that we can ip over to reveal the Ace of clubs.
The following slides are a walkthrough of the demonstration done in class. You will need this walkthrough to do your rst homework assignment.
Yellow Bubbles mean do something. Green Bubbles Red Bubbles are just for mean important! information.
Green Bubbles with small text is for minor notes.
Xcode 5 can be used to develop both iOS and Mac OSX applications.
Click on the Single View Application template. It creates a simple MVC application. These buttons are used to select a template which Xcode 5 uses to generate some code to get you started.
Our rst application is going to be a Card Matching Game These elds describe your project. Well be lling them in during the next few slides. The name of our project is going to be Matchismo so type that in here.
This will appear in the copyright at the top of all code les you create.
Here you can enter CS193p or Stanford or Bobs Awesome App House.
Enter edu.stanford.cs193p.yoursunetid
Using an entitys reverse DNS lookup string is a pretty good way to get a unique identier.
Enter CardGame as the prex for the name of the Controller this template is going to generate for us. Thus our Controller class will be called CardGameViewController.
Sometimes we would use the name of the application for this prex. In fact, older versions of Xcode would automatically do this whether we wanted it or not.
We dont want the names of the classes generated by the template to be too generic (e.g. ViewController). Thats why we specify this prex.
Set the Device were developing for to iPhone. Our rst application is going to be for the iPhone (not iPad). At least for starters.
A Universal application runs on both iPhone and iPad. In a Universal application, the iPad and the iPhone each has its own UI design (since they have different UI idioms). Xcode provides tools for designing two different UIs in the same application.
We will hopefully be covering source control in this course. But not for this rst project, so leave this switch turned off. If you dont have a Developer folder in your home directory, you can create it with this New Folder button.
Navigate to a directory called Developer in your home directory (create it if needed). Then click Create to create your project directory inside ~/Developer.
Congratulations, youve created your rst iOS Application! Youll probably want to make this window as big as possible. Xcode loves screen real-estate! Theres a lot of stuff in this window, but we wont be covering any of it in this rst application.
The Single View Application template we chose at the beginning has created a simple MVC for us. Our MVCs View is inside Main.storyboard.
CardGameViewController.[mh]
Well have to create our MVCs Model ourselves later. Lets open up and look at our MVCs View by clicking on Main.storyboard.
CardGameAppDelegate.[mh]
This should be selected. If its not, that would explain why youre not seeing your MVCs View.
This View is sized for the iPhone 5 (i.e. taller). Were going to design for the iPhone 4.
This area here is called the Document Outline. Were going to close it to make space. Well look at the Document Outline in detail later in the course.
Click here to switch to a iPhone 4-sized View. Close the Document Outline by clicking here.
This is the Navigator. It shows all the les in your project in a hierarchical arrangement of folders. The arrangement of folders is conceptual, it does not necessarily match whats in the le system. This area can also show symbols, search results, breakpoints, issues, etc. (see the icons at the top). This button shows/hides the Navigator. Click here to show the Utilities Area (if its not already showing).
Utilities Area The top part of this area shows information (identity, attributes, help, connections, dimensions) about the currently selected thing (le, object, etc.) at the left. The bottom is a library of items (objects, code snippets, le templates).
Stanford CS193p Fall 2013
Drag this bar up (if necessary) to expose the Library Area. Click here (if necessary) to select the Objects Palette.
File Inspector Shows information about the le containing the selected item. Logs Every time you build/run, a log of it is saved. Access old ones here. Breakpoints Well cover the debugger later. Threads Well cover multithreading later too. Tests Well cover Unit Testing later. Issues Compiler warnings/errors, etc. Search Find/replace in les in your Project. Class Hierarchy Quick Help If the selected item at the left has some documentation reference, this shows a summary version of it. Media Library Images, sounds, etc. Object Library Buttons, text elds, controllers, etc.
File Template Library Templates for storyboards, classes, etc. Code Snippet Library Snippets of code for common tasks.
This slide is just for reference. Dont worry about all these details for now.
Its time to start building the user-interface in our MVC View. Were building a card game, so well start with our rst card. Well use a button to represent it.
The Objects Palette contains a bunch of objects you can use to build your View.
If you are not seeing Button in the list, try clicking on your View.
Stanford CS193p Fall 2013
Notice the dashed blue lines which Xcode displays as you drag which help you line things up nicely.
Identity Inspector Set the class of the selected item. Attributes Inspector See and set the attributes of the selected item. Click on this. Size Inspector Position and size the selected item. Connections Inspector Connections between your View and Controller.
There are all kinds of attributes about the button you can set. Well do this in a moment.
Drag this back down to make more room for the Attributes.
Before we set the attributes of our Button, lets set the attributes of our Views background.
Hopefully the Attributes Inspector now shows the attributes of the area which is at the root of our MVCs View.
Lets change the Background color of the root of our MVCs View by clicking here ... This is what buttons look like usually in iOS 7. Think of them sort of like links in a web page. Were going to change the look of the button dramatically (to look like a card) using images, but thats actually somewhat unusual.
Moss is a good color because its sort of like the green felt of a card table.
The Buttons attributes should appear here. Notice these little white resize handles. Seeing these is another way to know the Button is selected.
Next were going to set a background image for the Button (to a blank white card).
Stanford CS193p Fall 2013
This is where all the images in your application go. Click on it.
The front of our card is going to have a background of a blank white card and the back is going to have a Stanford logo on it. We need to drag those 2 images into here so we can use them on the Button.
Bring up the Finder with the images we want to use (linked in your homework assignment).
Then drag them (one by one or all at the same time) into Xcode.
You may not want the name of the image (as referred to in your code) to be the same as the name of the le. You can simply double-click on it and change the name.
Well use the name cardfront to refer to the buttons background when the card is face up.
Well change the name of the image on the back of the card from stanford to ...
... cardback
Notice that theres a 1x version and a 2x version of all images. The 1x version is for non-Retina devices and the 2x version is for Retina devices. You should have both.
Items can also be deleted from this list by right clicking on them and choosing Remove.
Sometimes the Retina version might be able to show more detail. Showing the tree here is sort of an extreme example just to demonstrate this.
Hi-res Stanford Logo.
Lets do the same for the hi-res version of the background of the front of the card.
Its hard to see these two since they are blank, but they are white rounded rectangles. Youll be able to see them better in the UI.
Click here to change the Buttons background image. Any images we added to the Images asset library will be listed here. Choose cardfront.
Click on one of the resize handles to resize the button to t the background image.
If you press and hold on one of the resize handles, you can see the size of the button.
... the requires you to choose Special Characters ... from the Edit menu in Xcode to get this window ...
A button can show different attributes in different states. Were going to only set Default attributes. The Default attributes are what will show in any button state that does not have specic attributes set.
This is not quite what we want. We want a little bigger font and for the A to be black, not blue.
... or for even more font control, we can click on this T ...
... and then change the font, its family or style, and size.
Well leave this set to the System font for now.
Typography is crucially important to iOS 7 user-interface design. Well talk about that (especially these Text Styles) later in the course.
Changing the text color is simple, just click on the Text Color control.
Our UI is built for 3.5-inch Retina iPhone, so youll probably want to make sure thats set in this popup.
Lets Run our application for the rst time and see what it looks like. Click on this Play button to run.
Color is now black.
If you press and hold this Run button, other run options will be available, but were just using plain Run for now.
Notice this area below automatically appears when you run your application. It contains the debugger area and the console output. Well cover that later.
Debugger
Console
Stanford CS193p Fall 2013
A separate application, the iOS Simulator, will launch and your application will run.
You can click on the button, but it will not change because we havent coded what we want the button to do when we click on it.
Stanford CS193p Fall 2013
This simulator is not exactly like a device, but its close. You can even hit the home button ...
Click this Stop button to stop running your application in the Simulator.
If the debug area at the bottom does not automatically disappear when you stop running, click here.
Click here to show the Assistant Editor. When an MVC View is showing, the Assistant Editor will bring up the code for the corresponding MVC Controller. Thats exactly what we want.
This is the code for our MVCs Controller. So far its just some stubs created by Xcode based on the template we chose at the start.
You can adjust the space between the two panes of the editor by just dragging the space between them.
Stanford CS193p Fall 2013
If you want to edit the header le of the Controller (to make something public for example), you can switch it here.
These two methods are part of the View Controller Lifecycle. Well talk about that in-depth next week. For now, were not going to use them.
Now its time to connect the button to our Controller so that touching the button ips the card over. Believe it or not, you connect your View to your Controller by directly dragging from objects in your View into your source code. Sounds crazy, I know ...
Hold down the ctrl key while dragging from the card button into the @implementation block somewhere.
If you do not hold down the ctrl key, this will simply drag the button around. Be sure to drag somewhere between the @implementation and the @end.
You can drag from your View to the header (.h) le of your Controller if you want to make a public connection (rare).
Stanford CS193p Fall 2013
When you let go of the mouse, this dialog will appear. It wants to know some details about the action message this button is going to send when touched.
We know that the sender of this action is a UIButton. If this action could be sent from objects of another class (e.g. a UISlider), we could leave the type id (which means an object of any (unknown) class).
Specifying that we know the class of the sender makes it easier for the compiler to check that the code in our action method is not faulty. Well talk more about this type id next week.
Stanford CS193p Fall 2013
Action methods are allowed to have either no arguments, one argument (the object sending the message), or even two arguments (the sender and the touch event provoking the action). In this case, though, what we want is just the sending button so that we can change it when it is touched.
Normally buttons send their action when a touch that started by landing on them goes up while still inside the buttons boundaries. Thats what we want.
Once youve set up the action as shown, click Connect to create the action method.
The only argument to this method is the object that is sending the message to us (us is the Controller). In the previous slide, we made it clear that it is a button, so thats why the type of this argument is UIButton (instead of id).
The name of this method is actually touchCardButton:, not touchCardButton. This methods return type is actually void, but Xcode uses the typedef IBAction instead just so that Xcode can keep track that this is not just a random method that returns void, but rather, its an action method. Apart from that, IBAction is exactly the same thing as void.
Highlighted!
Mouse over (do not click) this little icon. The object in the View that sends this message will highlight.
Our implementation of this method is quite simple. Were just going to change the text on the button to be blank and change the background image to be our card back (the Stanford logo), thus ipping the card over to its back.
Lets start by declaring a local variable called cardImage to hold the cardback image (the Stanford logo we imported). The local variable is a pointer to an instance of the class UIImage which represents a JPEG, PNG, TIFF or other image.
Uh-oh, a warning!
UIImage has a class method called imageNamed: which creates an instance of UIImage given the name of an image in the image assets library. We just specify cardback (what we called the Stanford logo in the assets library).
object.
Xcode is constantly parsing your code in the background. Thus, warnings and errors will appear in this gutter without your having to explicitly build your project.
To get more details about a warning or error, click on the triangle. This warning is correct (we are not using the local variable cardImage yet), but nothing to worry about since well be using it in a moment.
Stanford CS193p Fall 2013
Next we are going to set the background image of the card button to be that Stanford logo (an instance of which is now stored in cardImage). To do that, we need to send a message to the UIButton that sent this touchCardButton: message to us.
The argument sender is the UIButton sending this message.
The syntax for sending a message in Objective-C starts off with [, then a pointer to the instance of the object to send the message to ...
... then the name of the message with the arguments interspersed ... We only have to type the rst few letters of the message and Xcode will immediate suggest matching method names.
After choosing the method you want, it should ll that method in and highlight the rst argument. This argument is obviously the image to set as the background of the UIButton.
For us, thats the cardImage local variable.
Stanford CS193p Fall 2013
Again, you need only type a couple of characters before Xcode will suggest what you want. Then double-click on the list or hit TAB once the one you want is selected.
Notice that there are LOTS of things that start with the two letters ca, but that Xcode is pretty smart about guessing which one you intend based on context.
Stanford CS193p Fall 2013
The setBackgroundImage:forState: method asks for the state because you can set different background images for selected, highlighted or disabled states of the UIButton.
Were just going to set the background for the default (Normal) state so it will be that way in all button states.
Stanford CS193p Fall 2013
Start typing UIC and youll get close to what we want (UIControlStateNormal) ...
Stanford CS193p Fall 2013
Easy! But we dont actually need this local variable. We can just embed that message-send in-line.
This line of code is so long that it is wrapping now. And not really in a great spot.
... and press the return key. Notice that Xcode lined up the two colons in the message name!
You should always line up the colons when you manually wrap the arguments of a method (in other words, dont undo what Xcode will do for you).
Stanford CS193p Fall 2013
To ip the card over not only do we need to set the background image to the Stanford logo, we need to get rid of the A.
Forgetting the @ before the is a very common coding error. without the @ is a const char *. const char *, while legal, is almost never used in Objective-C.
Again, we specify that we are setting this title for the default (Normal) button state.
... but now when you touch the button, it ips the card over.
Checking the length of an NSString to see if it is blank is cool because it works whether the string is the empty string (i.e. @) or is nil.
... (note that we dont have to type the [, since Xcode automatically adds it when we type ]) ...
Stop!
Lets take a little timeout to talk about documentation. There are numerous ways to transition to the documentation, but an easy one is to use the ALT key.
Hold down the ALT key and hover your mouse over something like currentTitle.
A dashed line should appear underneath and the cursor should be a question mark.
Well
lecture.
TIP: If you ever accidentally navigate away from your source code, you can click on this back button to get back.
Click on the UIButton Class Reference link in this little mini-documentation window to get more detailed documentation.
You can also hold down the ALT key and double-click on a term go directly to the documentation.
You should explore what is here. It is substantial. Being able to maneuver through the documentation is critical to success in iOS Development.
You can click on the many links here, like UIImage ...
You can also search for classes, methods, or just general topics of interest.
So weve learned how our Controller can react to the user manipulating an element of our MVCs View. Next lets learn how the Controller can proactively communicate with an element of the View.
And bring back the Object Palette by dragging this bar up from the bottom.
Were going to have a bit of text in our UI which shows how many times weve ipped the card. A UILabel is the UIKit class we want (it displays small bits of uneditable text).
Drag one into the lower left corner of the View. Use the blue guidelines to place it.
The Attributes Inspector has now changed to show attributes of the Label.
Grab a resize handle and make this UILabel very wide (again, use the blue guidelines).
Stanford CS193p Fall 2013
... to Flips: 0.
Were actually going to set this text entirely from our code, but Flips: 0 is what is going to appear when our application rst launches.
Now we have to connect this label to our Controller. We do this by dragging to our code again (but to the @interface instead of the @implementation).
Stanford CS193p Fall 2013
Hold down the CTRL key while dragging the mouse from the label to the code (then let go). Since were creating a @property here, we drag somewhere between the @interface and the @end. This @property were going to create is called an outlet property or outlet for short.
Stanford CS193p Fall 2013
The @property created by this process is weak because the MVCs View already keeps a strong pointer to the UILabel, so theres no need for the Controller to do so as well. And if the UILabel ever left the View, the Controller most likely wouldnt want a pointer to it anyway (but if you did want to keep a pointer to it even if it left the View, you could change this to strong (very rare)).
Stanford CS193p Fall 2013
IBOutlet is a keyword Xcode puts here (similar to IBAction) to remind Xcode that this is not just a random @property, its an outlet
(i.e. a connection to the View). The compiler ignores it. Otherwise this syntax should all be familiar to you.
Highlighted!
Just like with an action, you can mouse over this icon to see what the outlet connects to.
Stanford CS193p Fall 2013
It is also possible to see the connections between your View and Controller by right-clicking on an element in your View.
For example, right-click on the Flips:0 label. Here are all the connections to/from this UILabel. Notice the outlet flipsLabel.
You can disconnect connectoins by clicking on these little xs. But dont do it now! If you do so accidentally, just drag again from the View element to the appropriate code (method or @property).
It is a source of annoying bugs to forget to disconnect a nolonger-being-used connection. At runtime, youll get a crash with a complaint like no such method, touchCardButton:.
If you mouse over something in this dialog, it will show what it is connected to.
You can also right-click on your Controller itself (and thus see all connections) by using this icon.
Stanford CS193p Fall 2013
This title bar says that we are looking at the connections to/from our Controller.
Highlighted!
Mouse over.
Mouse over.
Highlighted! This is an automatically connected outlet from your Controller to the top-level of your View. Well talk about that later in the course.
Lets hide the Utilities area again to make more room for code.
We are going to keep track of the number of ips of the card using a new @property.
Nothing special about this @property, its just an integer. We could use NSInteger or NSUInteger here, but were using int, just to show doing so.
And well just increment it each time we ip the card. Notice that we can use ++ notation just like with a variable. This is the same as self.flipCount = self.flipCount+ 1. In other words, self.flipCount++ invokes both the getter and the setter for the flipCount @property.
But how can we coordinate the flipCount @property with the flipsLabel UILabel? Easy! Well use the setter of the flipCount @property. This is yet another advantage of using @property instead of accessing instance variables directly. This is what the setter for flipCount would normally look like.
Well just add this one line of code to set the text @property of the flipsLabel to a string formatted to include the flipCount.
Now any time the flipCount @property changes, the flipsLabel UILabel will get updated.
Advanced thinking: note that we use self.flipCount here instead of just _flipCount. Imagine if a subclass wanted to control the value of flipCount by overriding the getter but still benet from this methods display of it. Subtle.
Stanford CS193p Fall 2013
While were here, lets take another aside to look at a debugging technique. We can output something to the console any time we want.
We do it using the C function NSLog(). The rst argument to NSLog() is a @ printf-like format string specifying what to output. The rest of the arguments are the values matching up with the %s in the format string.
The rst argument to NSLog() must always be an @. Not any other kind of NSString. Remember, the console will automatically appear at the bottom of the screen when you run.
Stanford CS193p Fall 2013
Touch
Touch again.
Changes again.
Stop. Well this is all wonderful, but its sort of boring since it only shows the A all the time. If only we had a Deck of PlayingCards to drawRandomCard from, we could make each ip show a different card. Hmmm ...
We need to add all the classes from the Model we created in lecture (Card, Deck, PlayingCard and PlayingCardDeck).
Whenever we want to add a le to our project in Xcode (of any kind), we use the File > New > File ... menu item.
New File ... can be used to add all sorts of things (database schema les, storyboards, etc.). In this case, we want the default: Objective-C class.
Its important to be thoughtful about where you put your class les. In this case, wed like to group them in a Model folder (just to show you how its done).
Another option would have been to put all of your class les at this top level.
Here is a (blank) Card class. You will have to go through the slides from earlier and type in the implementation of Card. It is important to type the code in (not copy/paste it from somewhere) so that you gain experience with entering code in Xcode.
Stanford CS193p Fall 2013
You can put the header le in either the left or right side of the Assistant Editor as you prefer.
You can choose which goes where by clicking on the name of the le at the top of the pane in question.
Choosing the header or implementation from the Counterparts menu at the top of the pane ensures that Xcode will continue to always match the implementation up with the interface (or vice versa), even if you change what is in the left pane.
Select both Card.h and Card.m and then right-click on them to get this menu, then choose New Group from Selection.
... to Model.
A group in the File Navigator can be linked to a directory in the lesystem or not, as you prefer. You control this from the File Inspector in the Utilities area.
You can drag things around in the File Navigator to put them in whatever order you want.
For example, often well drag the AppDelegate.[mh] into Supporting Files group since we rarely edit them.
If you explicitly click on Card.h in the File Navigator, it will show it in the left pane of the Assistant Editor.
And Card.m will automatically appear in the right pane as long as Counterparts is selected here.
Type in the code for Card.[mh]. Now lets move on to creating templates for the Deck, PlayingCard and PlayingCardDeck classes.
Stanford CS193p Fall 2013
Whew! We did all this so that we could have each card not be A. Your homework is to make each ip draw a new random card. One of the rst things youll want is a @property for a Deck. Good luck!
Stanford CS193p Fall 2013
Coming Up
Needs more Card Game! Also next week ...
Your homework will be to have that single card ip through an entire Deck of PlayingCards. Next week well make multiple cards and put in logic to match them against each other. Objective-C language in depth Foundation classes: arrays, dictionaries, strings, etc. Dynamic vs. static typing Protocols, categories and much, much more!