iOS 7 TextKit - How to insert images inline with text?

IosIos7Textkit

Ios Problem Overview


I am trying to get the following effect using a UITextView:

enter image description here

Basically I want to insert an image between text. The image can simply just take up 1 row of space so there is no wrapping necessary.

I tried just adding a UIView to the subview:

UIView *pictureView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 25, 25)];
[pictureView setBackgroundColor:[UIColor redColor]];
[self.textView addSubview:pictureView];

But it seems to float over the text and cover it.

I did a bit of reading on exclusion paths which appears to be one way of implementing this. However, I don't want to absolutely position the image - instead, it should flow with the text (similar to how <span> behaves in HTML).

Ios Solutions


Solution 1 - Ios

You'll need to use an attributed string and add the image as instance of NSTextAttachment:

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"like after"];

NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIImage imageNamed:@"whatever.png"];

NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment];

[attributedString replaceCharactersInRange:NSMakeRange(4, 1) withAttributedString:attrStringWithImage];

Solution 2 - Ios

@bilobatum's code converted to Swift for those in need:

let attributedString = NSMutableAttributedString(string: "like after")

let textAttachment = NSTextAttachment()

textAttachment.image = UIImage(named: "whatever.png")

let attrStringWithImage = NSAttributedString(attachment: textAttachment)

attributedString.replaceCharacters(in: NSMakeRange(4, 1), with: attrStringWithImage)

Solution 3 - Ios

You could try using NSAttributedString and NSTextAttachment. Take a look at the following link for more details on customising the NSTextAttachment in order to resize the image. http://ossh.com.au/design-and-technology/software-development/implementing-rich-text-with-images-on-os-x-and-ios/

In my example I resize the image to fit the width, in your case you may want to resize the image to match the line height.

Solution 4 - Ios

Expanding on @bilobatum's answer, and using this category from another question. I cooked this up:

Usage:

UILabel *labelWithImage = [UILabel new];
labelWithImage.text = @"Tap [new-button] to make a new thing!";
NSAttributedString *stringWithImage = [labelWithImage.attributedText attributedStringByReplacingOccurancesOfString:@"[new-button]" withImage:[UIImage imageNamed:@"MyNewThingButtonImage"] scale:0];
labelWithImage.attributedText = stringWithImage;

Implementation:

@interface NSMutableAttributedString (InlineImage)

- (void)replaceCharactersInRange:(NSRange)range withInlineImage:(UIImage *)inlineImage scale:(CGFloat)inlineImageScale;

@end

@interface NSAttributedString (InlineImages)

- (NSAttributedString *)attributedStringByReplacingOccurancesOfString:(NSString *)string withInlineImage:(UIImage *)inlineImage scale:(CGFloat)inlineImageScale;

@end

.

@implementation NSMutableAttributedString (InlineImages)

- (void)replaceCharactersInRange:(NSRange)range withInlineImage:(UIImage *)inlineImage scale:(CGFloat)inlineImageScale {
    
    if (floorf(inlineImageScale) == 0)
        inlineImageScale = 1.0f;
    
    // Create resized, tinted image matching font size and (text) color
    UIImage *imageMatchingFont = [inlineImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    {
        // Font size
        NSDictionary *attributesForRange = [self attributesAtIndex:range.location effectiveRange:nil];
        UIFont *fontForRange = [attributesForRange valueForKey:NSFontAttributeName];
        CGSize imageSizeMatchingFontSize = CGSizeMake(inlineImage.size.width * (fontForRange.capHeight / inlineImage.size.height), fontForRange.capHeight);
        
        // Some scaling for prettiness
        CGFloat defaultScale = 1.4f;
        imageSizeMatchingFontSize = CGSizeMake(imageSizeMatchingFontSize.width * defaultScale,     imageSizeMatchingFontSize.height * defaultScale);
        imageSizeMatchingFontSize = CGSizeMake(imageSizeMatchingFontSize.width * inlineImageScale, imageSizeMatchingFontSize.height * inlineImageScale);
        imageSizeMatchingFontSize = CGSizeMake(ceilf(imageSizeMatchingFontSize.width), ceilf(imageSizeMatchingFontSize.height));
        
        // Text color
        UIColor *textColorForRange = [attributesForRange valueForKey:NSForegroundColorAttributeName];
        
        // Make the matching image
        UIGraphicsBeginImageContextWithOptions(imageSizeMatchingFontSize, NO, 0.0f);
        [textColorForRange set];
        [inlineImage drawInRect:CGRectMake(0 , 0, imageSizeMatchingFontSize.width, imageSizeMatchingFontSize.height)];
        imageMatchingFont = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }
    
    // Text attachment with image
    NSTextAttachment *textAttachment = [NSTextAttachment new];
    textAttachment.image = imageMatchingFont;
    NSAttributedString *imageString = [NSAttributedString attributedStringWithAttachment:textAttachment];
    
    [self replaceCharactersInRange:range withAttributedString:imageString];
}

@end

@implementation NSAttributedString (InlineImages)

- (NSAttributedString *)attributedStringByReplacingOccurancesOfString:(NSString *)string withInlineImage:(UIImage *)inlineImage scale:(CGFloat)inlineImageScale {
    
    NSMutableAttributedString *attributedStringWithImages = [self mutableCopy];

    [attributedStringWithImages.string enumerateOccurancesOfString:string usingBlock:^(NSRange substringRange, BOOL *stop) {
        [attributedStringWithImages replaceCharactersInRange:substringRange withInlineImage:inlineImage scale:inlineImageScale];

    }];
    
    return [attributedStringWithImages copy];
}

@end

Solution 5 - Ios

Problem solution in simple example is enter image description here

let attachment = NSTextAttachment()
attachment.image = UIImage(named: "qrcode")

let iconString = NSAttributedString(attachment: attachment)
let firstString = NSMutableAttributedString(string: "scan the ")
let secondString = NSAttributedString(string: "QR code received on your phone.")

firstString.append(iconString)
firstString.append(secondString)
    
self.textLabel.attributedText = firstString

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAndy HinView Question on Stackoverflow
Solution 1 - IosbilobatumView Answer on Stackoverflow
Solution 2 - IosJustin VallelyView Answer on Stackoverflow
Solution 3 - IosDuncan GroenewaldView Answer on Stackoverflow
Solution 4 - IosStian HøilandView Answer on Stackoverflow
Solution 5 - IosPrabhat KaseraView Answer on Stackoverflow