Question

In Xcode 5 I have created a single view iPhone app and checked it into GitHub.

I would like to display 5 draggable tiles with random letters and their values.

I would prefer to define the tile inside the Storyboard and then instantiate it (5 times) from there - because this way it is easy for me to edit the tile (for example move the labels inside the tile).

Currently my project looks like this (here fullscreen of Xcode):

Xcode screenshot

At the moment have just one tile in the Storyboard and it is draggable:

app screenshot

I have added a Tile class, but don't know how to connect its outlets (because I can only ctrl-drag to the ViewController.h, but not to the Tile.h):

Here Tile.h:

@interface Tile : UIView

// XXX How to connect the outlets?
@property (weak, nonatomic) IBOutlet UIImageView *background;
@property (weak, nonatomic) IBOutlet UILabel *letter;
@property (weak, nonatomic) IBOutlet UILabel *value;

@end

And Tile.m:

#import "Tile.h"

static NSString* const kLetters =  @"ABCDEFGHIJKLMNOPQRSTUWVXYZ";
static UIImage* kTile;
static UIImage* kDragged;

@implementation Tile

+ (void)initialize
{
    // do not run for derived classes
    if (self != [Tile class])
        return;

    kTile    = [UIImage imageNamed:@"tile"];
    kDragged = [UIImage imageNamed:@"dragged"];
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        NSString* randomLetter = [kLetters substringWithRange:[kLetters rangeOfComposedCharacterSequenceAtIndex:random()%[kLetters length]]];
        int randomInteger = (int)arc4random_uniform(10);

        _background.image = kTile;
        _letter.text = randomLetter;
        _value.text = [NSString stringWithFormat:@"%d", randomInteger];
    }
    return self;
}

@end

Finally ViewController.m:

#import "ViewController.h"

static int const kNumTiles = 5;

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];

    for (int i = 0; i < kNumTiles; i++) {
        // TODO: add a Tile to the VC here
    }
}

- (IBAction)dragTile:(UIPanGestureRecognizer *)recognizer
{
    UIView *tile = recognizer.view;

    if (recognizer.state == UIGestureRecognizerStateBegan ||
        recognizer.state == UIGestureRecognizerStateChanged) {

        CGPoint translation = [recognizer translationInView:[tile superview]];

        [tile setCenter:CGPointMake(tile.center.x + translation.x,
                                    tile.center.y + translation.y)];

        [recognizer setTranslation:CGPointZero inView:tile.superview];

        // TODO: instead of tile.png display dragged.png with shadow
    }
}

@end

In the latter file, I don't know

  1. How to instantiate 5 tiles from the Storyboard?
  2. How to display shadow (the dragged.png) when dragging a tile?

UPDATE:

As suggested by Fogmeister (thanks!) I have added a new file Tile.xib

(Selected in Xcode menu: File -> New -> File... -> User Interface -> View)

Then I've set the Custom Class to Tile, but where can I set the (square) dimensions?

Xcode screenshot

(Here fullscreen)

UPDATE 2:

For Tile.xib I've set Size to Freeform, Drawing to Opaque and dimensions to 100 x 100 (here fullscreen):

Xcode screenshot

Why does my custom UIView have white corners? How to make the background transparent?

app screenshot

Also I wonder, how to switch of the display of the battery in Interface Builder?

Was it helpful?

Solution

How to instantiate 5 tiles from the storyboard.

I think the thing to realise here is that storyboards are not the solution for everything but rather should be used with the suite of tools that already existed.

For instance. Creating multiple instances of views in this way is not something that can be done very well using Storyboards. Storyboards should be thought of as providing the overall backbone of the app.

To do this I'd do it one of two ways...

First Way

Create a new NIB file called Tile.xib and layout your single Tile view in there. Connect the outlets up to the Tile class file. Now in your view controller you can load the Tile class using the nib for layout...

Tile *tile = [[[NSBundle mainBundle] loadNibNamed:@"Tile" owner:self options:nil] firstObject];
tile.frame = //blah
[self.view addSubview:tile];

Second Way

Or forget the nib and load the Tile view all in code in your Tile.m file. Then load it like...

Tile *tile = [[Tile alloc] initWithFrame:someFrame];
[self.view addSubview:tile];

How to display shadow (the dragged.png) when dragging a tile?

For this you need to set the shadow on the layer of the tile view...

tile.layer.shadowColor = [UIColor blackColor].CGColor;
tile.layer.shadowRadius = 4.0;
// etc...

You can read more about shadows here...

https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/CALayer_class/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004500-CH1-SW78

Edited after comment

To do this I would have a BOOL property on Tile.h called something like isDragging. Or even an enum called TileState with TileStateDragging and TileStateStatic.

Then have a method...

- (void)setDragging:(BOOL)dragging
{
    _dragging = dragging;

    if (_dragging) {
        //set to the shadow image.
    } else {
        //set the none shadow image.
    }
}

Some other things to note

Currently you have code inside initWithFrame of the Tile class but you are loading the class using a nib (storyboard in this case). This will run the method initWithCoder not initWithFrame so this code will never get run.

If you want to run code when the class is created you might be best using the method awakeFromNib instead.

OTHER TIPS

The problem seems to be solved but I'd still put in my 2 pennies and show you another solution just so that you have a complete picture. You can instantiate a UICollectionView in the storyboard and define a UICollectionViewCell prototype inline (in the storyboard). The cell is also a regular view so you can put your complete tile view hierarchy in there (all in the storyboard). You will then need to define a custom UICollectionViewLayout subclass that will provide layout attributes for the tiles (those can change over time and may be animated just like any other views). The use of collection view will impose a structure on your code that will:

  1. scale to any number of tiles,
  2. support tasks like animated insertion and deletion of the tiles (you still need to write code in your layout subclass but all the APIs are already defined for you and you don't need to invent the design),
  3. separate the layout code from game logic (which is of course not exclusive to this solution but still nice to get without even thinking of it :).

This looks like an overkill for this particular toy project but you might need to consider collection view for more complex situations.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top