Author Archives: psgouros

custom transitions

here’s the basic recipe for doing custom transition animations. (this does not include use of newer transition coordinator, just simple transitions.)

within a UIStoryboardSegue object, you are handed the two view controllers at either end of the segue.  So:

  1. you take a snapshot of your sourceViewController view, which is a UIView.
  2. add that snapshot as a subview of the destination view controller, (you may have to correct for the presence of navigation bars etc.)
  3. Push (or pop, if you’re unwinding,) the destination view.
  4. animate the snapshot where ever you like to get rid of it.
  5. then remove the snapshot from the destinationViewController view.

basically, it’s a magic trick. you’re swapping out one view controller for another that has been temporarily masked to look like the first one. and then you throw away the mask with a dramatic flourish. If you’re a fan of magic, a reasonable metaphor might be “The Pendragons” and their most famous trick.

in code, your custom UIStoryboardSegue code might look like:

-(void)perform
{
        UIView *source = [(UIViewController *)self.sourceViewController view];
        UIView *snap = [source snapshotViewAfterScreenUpdates:YES];
        [[self.destinationViewController view] addSubview:snap];
        [[self.sourceViewController navigationController] pushViewController:self.destinationViewController animated:NO];
        [UIView animateWithDuration:.5
                         animations:^{
                             //do fun animation with snap here...
                         }
                         completion:^(BOOL finished) {
                             //if you want to, you can put another UIView animate with duration here for multipart animations
                             [snap removeFromSuperview];
                         }];
}

important thing to note is the cast to (UIViewController *). Since most view controllers are custom objects, the initialization for UIStoryboardSegue defines the two as id to allow for this, so before you can send it messages, you have to tell the compiler what messages it can receive.  Alternatively, you could cast it to a id with a protocol, but you would have to make sure that you also defined any other methods that you might want to use in the protocol.  you might find it worth while to rewrite the init method of your custom segue to require a particular protocol and type as well.

CGContextAddArcToPoint()

void CGContextAddArcToPoint (
   CGContextRef c,
   CGFloat x1,
   CGFloat y1,
   CGFloat x2,
   CGFloat y2,
   CGFloat radius
);

Seems simple enough. the thing to remember is that the first point is the corner you would have been going to go to if you were doing a hard corner, and the second point is the point where you’re intending to go next.

so, if you were to draw an angle, you might do the following.

CGContextMoveToPoint(ctx, 10.0, 60.0);    CGContextAddLineToPoint(ctx, 60.0, 60.0);
CGContextAddLineToPoint(ctx, 60.0, 10.0);

to get:
Screen Shot 2013-12-20 at 12.43.49 PM

if you want to make the corner rounded, you would replace the middle point with an addArcToPoint like:

CGContextMoveToPoint(ctx, 10.0, 60.0);
CGContextAddArcToPoint(ctx, 60, 60, 60, 10, 20);
CGContextAddLineToPoint(ctx, 60, 10);

to get:
Screen Shot 2013-12-20 at 12.44.07 PM

note that despite telling the arc where you want to end up, you still have to draw the line to get there. otherwise you get the rounded corner and nothing else.  from this you can establish that addArcToPoint is basically drawing two straight lines that meet where your corner would have been, and then tries to wedge a circle of the requested radius into that corner. the arc that results from tangent to tangent, as well as the line from the previous point is what is actually drawn.  it does not, however, draw the line from the end of the arc to the next point. you have to do that yourself.  it’s important to note that if you want the line leaving the arc to be tangent to that arc, it should probably actually be a line to the second point that you defined in the addArcToPoint function.

one caveat to this is that if the starting point (the one in the moveTo…  line) and the first point in the addArcToPoint are the same, there will be no arc.

so if you want to make a circle you might do the following

CGContextMoveToPoint(ctx, 20, 20);
CGContextAddArcToPoint(ctx, 20, 10, 10, 10, 10);
CGContextAddArcToPoint(ctx, 0, 10, 0, 20, 10);
CGContextAddArcToPoint(ctx, 0, 30, 10, 30, 10);
CGContextAddArcToPoint(ctx, 20, 30, 20, 20, 10);

and you get:
Screen Shot 2013-12-20 at 12.44.49 PM

one last thing. it is up to you to make sure that the arc radius is small enough to fit into the corner that would have been drawn. otherwise it will draw the complete arc angle that you request, but it probably won’t be where you want it to be.  for example, this code:

CGContextMoveToPoint(ctx, 20.0, 50.0);
CGContextAddArcToPoint(ctx, 50, 50, 20, 20, 25);
CGContextAddLineToPoint(ctx, 20, 20);

gives you:
Screen Shot 2013-12-20 at 12.47.42 PM

where an arc radius of 5 would have given you:

Screen Shot 2013-12-20 at 12.52.08 PM

which is probably what you wanted… right?

oh the frustration…

the phrase “that couldn’t possibly happen” is a phrase that is going to live right up there with the question “did you try it on a clean machine?”

I have spent quite a few hours lately trying to figure out a not terribly obscure part of the UIDocument specification as it applies to exported UTIs and getting the finder and iTunes to recognize a file package as a package and not as a simple folder.

as it turns out, the documentation is likely written by people who probably answer yes to that question in the first paragraph…  that said, I would like to announce to anyone else who asks, that if you are trying to construct a UIDocument based application, and you seem to be having trouble getting your system to recognize that your carefully crafted package of document  parts should be recognized as a singular whole rather than a loose agglomeration of bits, it is probably that your system is old enough that it is looking through fuzzy glasses and getting confused. it is in need of a flea bath.  at the following link, there are some very coherent instructions on what to do about it.  oh the interconnectedness of things…

Latest Update says Ascension cant open files of type .nfo · Issue #6 · ByteProject/Ascension · GitHub.

I can’t tell you how well I’ll sleep now…

Alpha Go!

I’ve released my first alpha! I’ve actually seen it installed on someone else’s phone and it still works!

the hardest thing at the moment is looking at the product and stopping myself from adding another feature…

feature complete!

much refining and testing still to come, but I got animation frame cycling into the system which is a great improvement.

still need a name for the app. so if anyone randomly comes by, any worthwhile suggestions are welcome…

UITableViews

initially the mechanics of these were a bit of a mystery. API documentation leaves a lot to be desired…

The bird’s eye view of the process of working with a dynamic table goes something like this:

creating the table:

when ‘reloadData’ is called:

  1. the table delegate/datasource is asked “How many sections do you have?” and the data source must answer with a positive number.
  2. the table delegate/datasource is then asked by each section, “I’m in section x, how many cells should I have?”
  3. the table delegate/datasource is then asked by each individual cell, in order, “I’m cell  y from section x, what data should I have?”

and lo, all the cells are filled. at this point there is a one to one association between the data source and the visible table. from this point on, if this ever disagrees, the app will probably crash. these three questions come in the form of these three objective-c calls

  1. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  2. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  3. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

number three there comes with some boilerplate code when it is generated by Xcode, which is designed to manage only maintaining a relatively small subset of all possible cells. The intent is that a cell is memory intensive, and if you have a long list, most of which is off screen, you shouldn’t waste memory on stuff you can’t see. so the lines:
static NSString *CellIdentifier = @"simpleCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

are there to provide you with just the cells you need to fill at any given moment.
(an index path is a structure that is made up of two members, an array of integers that is the path, and a separate integer that is the length of the first array.  technically, indexPaths can be of any length, but in iOS, they are always of length 2. the first number is the section, and the second is the row in that section.)

editing the table:

this was the tricky bit. just because the table loads from the data source, and provides all the funky animated editing modes doesn’t mean that you don’t have to keep the data source synchronized manually.

the basic things that you can do when editing a table structure are:

  1. delete an item
  2. add an item
  3. move an item from one place to another

though it turns out this last thing is just a combination of the previous two.

again, when you do these things, several calls are made to the datasource from the table view to establish a) am I allowed to do this, and if so, b) what are you going to do about it? and it asks slightly different questions for 1 and 2 than it does for 3.

the first thing you have to do to edit the table is enter editing mode. this can be done programmatically, by calling  [self.tableView setEditing:NO animated:YES];
though happily a table view has an inherent ‘edit’ button you can activate by simply calling self.navigationItem.rightBarButtonItem = self.editButtonItem; during viewDidLoad somewhere.

once you do this, unless you’ve changed something, the table will now show an accessory in each cell to the right or left of the content.  to the left, assuming it’s a allowed will either be a green circle with a plus sign or a red circle with a minus sign to add or delete that row. and on the right side, if it’s allowed will be an icon of three horizontal bars that is presumably supposed to be a gripper (or some other iconic indication that you can reposition the cell.) which symbol goes where is controlled by the tableView asking it’s delegate the same questions, once each per cell. and they are:

  • - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
  • - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
  • - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath

the first, obviously, can I edit this row? which dictates whether you get the edit control on the left at all.  The second, which dictates which control it is. the first and third are, in fact, optional in that if the table view gets no answer, it assumes yes.  you only have to specifically say no. for the second one, if there is no answer, the assumed edit type is ‘delete’ .  To repeat, these are optional. you don’t have to implement them if you don’t want to do anything complicated.

However. assuming you have enabled editing at all, once you have performed an edit, the table view will inform its delegate of this and you should then do something about it. these are the methods that it calls to do so.

  • - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
  • - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath

The first says to execute the edit based on the style that we optionally set earlier, and the second is simply to move from one place to another. inside these implementations is where you are supposed to actually do the work to keep the view and the model synchronized.

for example, to delete a row from the array and the table view (note that the methods are plural! the first argument is an array.):
[self.listThings removeObjectAtIndex:[indexPath row]];
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];

and to move a row:
id thingy = [self.listThings  objectAtIndex:[fromIndexPath row]];
[self.listThings removeObjectAtIndex:[fromIndexPath row]];
[self.listThings insertObject:thingy atIndex:[toIndexPath row]];

interesting to note that with deleting (and adding) rows, you have to manually remove them from both the tableview and the model. where when you move a row, the table view is taken care of visually and you only have to alter the model to match. (well, you don’t have to, but it seems like a good idea.)

other than custom cells, this pretty much covers every basic thing you need to know to get table views working.

good luck. let me know if I’ve left anything out.