In this episode Sam shows us how to mix rich content such as images into a text view. Using Text attachments we can flow text around an image, select the image and delete or replace it, and more. Sam shows how to respond to layout changes and some advice on performance. Text Attachments are not for the faint of heart!
Episode Links Source Code NSTextAttachment Class Reference Setting up the Attachment Character override func viewDidLoad() { super.viewDidLoad() title = "Attachment" let attachmentCharacter = String(Character(UnicodeScalar(NSAttachmentCharacter))) let text = NSMutableAttributedString(string: "This is our image:\n\n\(attachmentCharacter)\n\nAfter the image.", attributes: [ NSFontAttributeName: UIFont.systemFontOfSize(18) ]) let attachment = NSTextAttachment() attachment.image = UIImage(named: "Photo") text.addAttribute(NSAttachmentAttributeName, value: attachment, range: NSRange(location: 20, length: 1)) textView.attributedText = text } Responding to layout changes override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let textRange = NSRange(location: 0, length: textView.attributedText.length) textView.attributedText.enumerateAttribute(NSAttachmentAttributeName, inRange: textRange, options: []) { [weak self] value, range, _ in guard let attachment = value as? NSTextAttachment else { return } if let bounds = self?.boundsForAttachment(attachment), layoutManger = self?.textView.layoutManager { attachment.bounds = bounds let invalidRange = NSRange(location: range.location, length: textRange.length - range.location) layoutManger.invalidateLayoutForCharacterRange(invalidRange, actualCharacterRange: nil) } } } // MARK: - Private private func boundsForAttachment(attachment: NSTextAttachment) -> CGRect? { guard let image = attachment.image else { return nil } // Assuming landscape let width = textView.textContainer.size.width - (textView.textContainer.lineFragmentPadding * 2) return CGRect(x: 0, y: 0, width: width, height: image.size.height / image.size.width * width) }