11

I'm trying to build a custom keyboard for iOS using images which I've put in as buttons. When I press a button, the image linked to the button is put into an attributed string which is loaded into an UiTextView inside the custom keyboard view. That is working.

The problem is that when I append a new image to the attributed string both the old and new images in the string are changing to the image I currently pressed on. I can't understand why the old images in the string are changing.

Any suggestions? I've tried using replaceCharactersInRange and insertAttributedString but can't get it to work. Here is the code (after viewDidLoad):

let textAttachment = NSTextAttachment()

let textView = UITextView(frame: CGRectMake(5, 5, 200, 40))
var attributedString = NSMutableAttributedString(string: "")
    
@IBAction func buttonPressed(button :UIButton) {
          
    let string = button.titleLabel?.text
    
    textAttachment.image = UIImage(named: "\(string!).png")!
    textAttachment.image = UIImage(CGImage: textAttachment.image!.CGImage!, scale: 6, orientation: .Up)
    let attrStringWithImage = NSAttributedString(attachment: textAttachment)
    attributedString.appendAttributedString(attrStringWithImage);
    
    textView.attributedText = attributedString;
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
knorrhane
  • 111
  • 1
  • 1
  • 5
  • 1
    You're re-using the same `textAttachment` for each image, that's your problem. Simply create a new `NSTextAttachment()` each time, and no need to store it. – Cœur Mar 19 '18 at 11:12

4 Answers4

5

We cannot simply append NSTextAttachment image .We have to store all the attached Image and concatenation to existing attributed String.

func buttonPressed(_ sender: Any) {

    let  button = sender as! UIButton
    let tag =  button.tag
    let attributedString:NSAttributedString!

    switch tag {
    case 2:
        attributedString = addAttributedText(text: (button.titleLabel?.text)!)
    case 3:
        attributedString = addAttributedText(text: (button.titleLabel?.text)!)
    case 4:
        attributedString = addAttributedText(text: (button.titleLabel?.text)!)
    default:
       attributedString = addAttributedText(text: "launch")
      }
        textView.attributedText = attributedString
    }

    func addAttributedText(text:String) -> NSAttributedString {
        textViewDidChange(textView)
        let axtractedImageAttribute = NSMutableAttributedString()
        for image in imageArray {
            let attachment:NSTextAttachment = NSTextAttachment()
            attachment.image = image
            attachment.setImageHeight(height: 20)
            let attachmentString:NSAttributedString = NSAttributedString(attachment: attachment)
            axtractedImageAttribute.append(attachmentString)
        }

        let attachment:NSTextAttachment = NSTextAttachment()
        attachment.image = UIImage(named: "\(text).png")
        attachment.setImageHeight(height: 20)
        let attachmentString:NSAttributedString = NSAttributedString(attachment: attachment)
        let attributedString:NSMutableAttributedString = NSMutableAttributedString(string:textView.text!)
        attributedString.append(axtractedImageAttribute)
        attributedString.append(attachmentString)
        return attributedString
    }

    //MARKS:-  Extract attachedImage
    func textViewDidChange(_ textView: UITextView)  {
        imageArray = [UIImage]()
        let range = NSRange(location: 0, length: textView.attributedText.length)
        if (textView.textStorage.containsAttachments(in: range)) {
            let attrString = textView.attributedText
            var location = 0
            while location < range.length {
                var r = NSRange()
                let attrDictionary = attrString?.attributes(at: location, effectiveRange: &r)
                if attrDictionary != nil {
                    let attachment = attrDictionary![NSAttachmentAttributeName] as? NSTextAttachment
                    if attachment != nil {
                        if attachment!.image != nil {
                            imageArray.append( attachment!.image!)
                        }
                    }
                    location += r.length
                }
            }
        }
    }

}

enter image description here enter image description here

Demo Reference

Shrawan
  • 7,128
  • 4
  • 29
  • 40
2

This is my extension Swift 4, to change image to attributed String with size and tint color change

public extension UIImage {

    func tint(with color: UIColor) -> UIImage {
        var image = withRenderingMode(.alwaysTemplate)
        UIGraphicsBeginImageContextWithOptions(size, false, scale)
        color.set()

        image.draw(in: CGRect(origin: .zero, size: size))
        image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
    }

    func toAttributedString(with heightRatio: CGFloat, tint color: UIColor? = nil) -> NSAttributedString {
        let attachment = NSTextAttachment()
        var image = self

        if let tintColor = color {
            image.withRenderingMode(.alwaysTemplate)
            image = image.tint(with: tintColor)
        }

        attachment.image = image

        let ratio: CGFloat = image.size.width / image.size.height
        let attachmentBounds = attachment.bounds

        attachment.bounds = CGRect(x: attachmentBounds.origin.x,
                                   y: attachmentBounds.origin.y,
                                   width: ratio * heightRatio,
                                   height: heightRatio)

        return NSAttributedString(attachment: attachment)
    }
}
YanSte
  • 10,661
  • 3
  • 57
  • 53
0

I found the problem, I need to have different variables for the NSTextAttachment (ie. textAttachment1, textAttachment2 etc.) otherwise it just uses the first image that was addressed.

knorrhane
  • 111
  • 1
  • 1
  • 5
0

This works for me:

let attributedStringTextAttachment = NSTextAttachment()
attributedStringTextAttachment.image = UIImage(named: "image")
Boomspot
  • 53
  • 1
  • 12