Daniel Hilgarth Daniel Hilgarth - 1 month ago 8
iOS Question

Auto-flowing layout with embedded controls and automatically breaking text

I am trying to create a layout for my iPhone and iPad app that automatically reflows based on dynamic data.
A bit more concrete, this means that I have several objects. Each object has a certain type and associated data. The data is what should be displayed, the type determines how it should be displayed. A type could be text, meaning it should be displayed as simple text without the possibility for interaction. Another type could be date, meaning it should be displayed as a control that lets the user select a date, when tapped.

Now, the problem is that the objects I want to display are dynamic in number and associated data, so I can't position the controls at static locations.

This jsfiddle shows what kind of layout I mean. Depending on the width of the

div
, the content automatically re-flows and the date input control appears in the middle of the text.

I couldn't find any control that supports a scenario like this - so how would I go about achieving this?

Answer

Here is an example of using CoreText to solve a partially similar problem. Perhaps that will point you in that direction.

- (CFArrayRef)copyRectangularPathsForPath:(CGPathRef)path 
                                   height:(CGFloat)height {
    CFMutableArrayRef paths = CFArrayCreateMutable(NULL, 0, 
                                                   &kCFTypeArrayCallBacks);

    // First, check if we're a rectangle. If so, we can skip the hard parts.
    CGRect rect;
    if (CGPathIsRect(path, &rect)) {
        CFArrayAppendValue(paths, path);
    }
    else {
        // Build up the boxes one line at a time. If two boxes have the 
        // same width and offset, then merge them.
        CGRect boundingBox = CGPathGetPathBoundingBox(path);
        CGRect frameRect = CGRectZero;
        for (CGFloat y = CGRectGetMaxY(boundingBox) - height; 
                     y > height; y -= height) {
            CGRect lineRect =
                   CGRectMake(CGRectGetMinX(boundingBox), y, 
                              CGRectGetWidth(boundingBox), height);
            CGContextAddRect(UIGraphicsGetCurrentContext(), lineRect);

            // Do the math with full precision so we don't drift, 
            // but do final render on pixel boundaries.
            lineRect = CGRectIntegral(clipRectToPath(lineRect, path));
            CGContextAddRect(UIGraphicsGetCurrentContext(), lineRect);

            if (! CGRectIsEmpty(lineRect)) {
                if (CGRectIsEmpty(frameRect)) {
                    frameRect = lineRect;
                }
                else if (frameRect.origin.x == lineRect.origin.x && 
                         frameRect.size.width == lineRect.size.width) {
                    frameRect = CGRectMake(lineRect.origin.x,                                                                                                                                      lineRect.origin.y,                                                                                                                                      lineRect.size.width, 
                                CGRectGetMaxY(frameRect) - CGRectGetMinY(lineRect));
                }
                else {
                    CGMutablePathRef framePath =
                                         CGPathCreateMutable();
                    CGPathAddRect(framePath, NULL, frameRect);
                    CFArrayAppendValue(paths, framePath);

                    CFRelease(framePath);
                    frameRect = lineRect;
                }
            }
        }

        if (! CGRectIsEmpty(frameRect)) {
            CGMutablePathRef framePath = CGPathCreateMutable();
            CGPathAddRect(framePath, NULL, frameRect);
            CFArrayAppendValue(paths, framePath);
            CFRelease(framePath);
        }           
    }

    return paths;
}