Dynamically defining a selector from a string

I’m used to calling a selector in iOS using the following format:

@selector(method_name_here:arguments)

However, I wasn’t aware that you could define a selector by passing in the name of the method at runtime. It turns out you can do it like this:

SEL selector = NSSelectorFromString(some_string);
result = [some_object performSelector:selector];
Advertisements

Clipping views using a Bezier path

I had a situation where I was rendering a chart, and I needed to clip a tooltip on the chart so that it didn’t get rendered outside of the chart plotting area.  We can use the CALayer on a view to clip a view, by setting the mask property of the layer like so:

CGRect clippingRect;
// Set clippingRect to the rectangle you wish to clip to

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:clippingRect];

// Create a shape layer
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.frame;

// Set the path of the mask layer to be the Bezier path we calculated earlier
maskLayer.path = maskPath.CGPath;

self.layer.mask = maskLayer;

A key thing to note is that if we just want to clip a particular sub view on a view, we can set the clipping area just for that sub view. This means that any other views are unaffected.

Embedding a hyperlink into CoreText

This was a problem in effectively two parts.  The first part was to style a section of my attributed string as a hyperlink (i.e. underlined, although in other cases I might have wanted to colour it blue).  How to do that is fairly well documented, and I won’t go over it in detail here.  Some useful tutorials on the topic are:

http://www.raywenderlich.com/4147/how-to-create-a-simple-magazine-app-with-core-text

http://www.cocoanetics.com/2011/01/befriending-core-text/

The second part of the problem was less well documented – I had to find some way of responding to user taps on links in the text.  I used a markup parser similar to that used in the Ray Wenderlich tutorial.  It used opening tags, and styled any text following that tag accordingly.  I defined a tag type for links.  Where I came across a link, I stored a custom attribute against that section of the text, which had a key of “link”, and a value of the URL against which to link against.

After rendering my core text, I used the following code to find the bounds on screen of the sections of the text which showed links.  When I found a link, I added a sub view with its bounds to handle taps on that section of the screen:

// Get the lines in our frame
    NSArray* lines = (NSArray*)CTFrameGetLines(frame);
    CFIndex lineCount = [lines count];

    // Get the origin point of each of the lines
    CGPoint origins[lineCount];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);

    for(CFIndex idx = 0; idx < lineCount; idx++)
    {
        // For each line, get the bounds for the line
        CTLineRef line = CFArrayGetValueAtIndex((CFArrayRef)lines, idx);

        // Go through the glyph runs in the line
        CFArrayRef glyphRuns = CTLineGetGlyphRuns(line);
        CFIndex glyphCount = CFArrayGetCount(glyphRuns);
        for (int i = 0; i < glyphCount; ++i)    {
            CTRunRef run = CFArrayGetValueAtIndex(glyphRuns, i);

            NSDictionary *attributes = (NSDictionary*)CTRunGetAttributes(run);
            if ([attributes objectForKey:@"link"])    {
                CGRect runBounds;

                CGFloat ascent;//height above the baseline
                CGFloat descent;//height below the baseline
                runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
                runBounds.size.height = ascent + descent;

                // The bounds returned by the Core Text function are in the coordinate system used by Core Text.  Convert the values here into the coordinate system which our gesture recognizers will use.
                runBounds.origin.x = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
                runBounds.origin.y = self.frame.size.height - origins[idx].y - runBounds.size.height;

                // Create a view which will open up the URL when the user taps on it
                LinkTapView *linkTapView = [[[LinkTapView alloc] initWithFrame:runBounds url:[attributes objectForKey:@"link"]] autorelease];
                linkTapView.backgroundColor = [UIColor clearColor];
                [self addSubview:linkTapView];
            }
        }
    }

The LinkTapView class was one I wrote myself. It was a subclass of UIView, to which I added a tap gesture recognizer. In its handleTap method, it opened up the specified URL.

LinkTap:

- (void)handleTap: (UITapGestureRecognizer*)sender  {
    UIApplication *application = [UIApplication sharedApplication];
    NSURL *url = [NSURL URLWithString: linkUrl];
    // If we can open the URL specified by the link, do so in a web browser
    if ([application canOpenURL:url])   {
        [[UIApplication sharedApplication] openURL:url];
    } else {
        NSLog(@"Unable to open URL: %@", linkUrl);
    }
}

Adding loading page to iOS app

I have an iOS app inside of which I am using a UINavigationController to create a story-style work flow.  The user goes through a set of pages until they reach the end.  This works well, but some of the pages take a long time to render for the first time.  We had the situation where the user would push a button to move to the next page, and then nothing would appear to happen for a few seconds while the next page was rendering.

This wasn’t going to be good enough as a user experience.  The approach we took to improve things was to display a loading page to the user while the next page was loading.  When the page was loaded, the loading screen faded out.

The approach we took for this was to add an overlay view as a subview of the view which was loading.  The overlay contained the progress indicator.  When it was no longer needed, we started an animation and set the alpha of the overlay view to 0.

As an approach, this was fine, but we still needed to update the code so that the new view was shown before it had finished rendering.  In the end, we achieved this by using the performSelector:withObject:afterDelay: method.  This meant that we could get the behaviour we wanted without needing to write multithreaded code.  The method calls the selector on the specified object on the current threads run loop.  It waits for the specified delay before queueing the selector.  In our app, this meant that the view was displayed immediately, and then after 0.1 seconds we would start rendering its contents.  The line to do this is shown below.

[self performSelector:@selector(renderView) withObject:nil afterDelay:0.1f]

This meant that we could display our loading overlay for that time, and then when the renderView method was done, we could dismiss it.

To display a nice progress indicator, we used the excellent MBProgressHUD project.