adding image to NSAttributedString makes the text not centre - ios

extension UILabel {
func setAttributedTextWithElements(objectsForLabel: [AnyObject]) {
let attributedString = NSMutableAttributedString()
for object in objectsForLabel {
if let string = object as? String {
attributedString.append(NSAttributedString.init(string: string))
} else if let imageForLabel = object as? UIImage {
let attachment = NSTextAttachment()
attachment.image = imageForLabel
let attributedImageString = NSAttributedString.init(attachment: attachment)
attributedString.append(attributedImageString)
let imageWidth = (attachment.image?.size.width)!
let imageHeight = (attachment.image?.size.height)!
attachment.bounds = CGRect(x: 0, y: font.descender, width:imageWidth, height:imageHeight)
}
}
attributedText = attributedString
}
Basically the text is not centred any more. If comment out the code for the image the label text is perfectly centred.
I'm passing an image and a string "20" to this method in the array.

Related

How to place image inside UILabel on start of UILabel text?

Hi I wanna add image round dot to some UILabel in my app.
I have code for adding the image. But I don't understand how I could put the image on the start of the UILabel rather than in the end of the label.
Any suggestion on this? Below is the code I use for it:
What should I add to place image on start of UILabel? I thought imageBehindText :false would fix it but it didn't.
extension UILabel {
/**
This function adding image with text on label.
- parameter text: The text to add
- parameter image: The image to add
- parameter imageBehindText: A boolean value that indicate if the imaga is behind text or not
- parameter keepPreviousText: A boolean value that indicate if the function keep the actual text or not
*/
func addTextWithImage(text: String, image: UIImage, imageBehindText: Bool, keepPreviousText: Bool) {
let lAttachment = NSTextAttachment()
lAttachment.image = image
// 1pt = 1.32px
let lFontSize = round(self.font.pointSize * 1.20) // rounded dot should be smaller than font
let lRatio = image.size.width / image.size.height
lAttachment.bounds = CGRect(x: 0, y: ((self.font.capHeight - lFontSize) / 2).rounded(), width: lRatio * lFontSize, height: lFontSize)
let lAttachmentString = NSAttributedString(attachment: lAttachment)
if imageBehindText {
let lStrLabelText: NSMutableAttributedString
if keepPreviousText, let lCurrentAttributedString = self.attributedText {
lStrLabelText = NSMutableAttributedString(attributedString: lCurrentAttributedString)
lStrLabelText.append(NSMutableAttributedString(string: text))
} else {
lStrLabelText = NSMutableAttributedString(string: text)
}
lStrLabelText.append(lAttachmentString)
self.attributedText = lStrLabelText
} else {
let lStrLabelText: NSMutableAttributedString
if keepPreviousText, let lCurrentAttributedString = self.attributedText {
lStrLabelText = NSMutableAttributedString(attributedString: lCurrentAttributedString)
lStrLabelText.append(NSMutableAttributedString(attributedString: lAttachmentString))
lStrLabelText.append(NSMutableAttributedString(string: text))
} else {
lStrLabelText = NSMutableAttributedString(attributedString: lAttachmentString)
lStrLabelText.append(NSMutableAttributedString(string: text))
}
self.attributedText = lStrLabelText
}
}
I got it to work. The problem was that I was setting the text in the storyboard(.xib). So this extension didn't change the image to the front even if the bool-val were false.
Simply set the text from the function-call and the 'false' value will trigger the image to be set in the start of the uilabel.
Example1 (what I did wrong):
// This is what I tried first!
label.addTextWithImage(text: "",
image: UIImage(named: embededIcon)!,
imageBehindText: false, // note! This is false.
keepPreviousText: true) // this was the problem!
Example2 (what got it to work!):
label.addTextWithImage(text: "putYourLabelTextHere!", // You have to put text here, even if it's already in storyboard.
image: UIImage(named: embededIcon)!,
imageBehindText: false,
keepPreviousText: false) // false, so the image will be set before text!
lazy var attachment: NSTextAttachment = {
let attachment = NSTextAttachment()
attachment.image = UIImage(named: "")
return attachment
}()
it will work for both remote and local image
if let imageUrl = imageUrl, imageUrl.count > 0, let url = URL(string: imageUrl) {
let imageAttachmentString = NSAttributedString(attachment: attachment)
attachment.bounds = CGRect(x: 0, y: (titleLabel.font.capHeight - 16) / 2, width: 16, height: 16)
BaseAPI().downloadImage(with: url, placeHolder: "") { [weak self] image, error, success, url in
if let image = image, let self = self {
DispatchQueue.main.async {
let finalString = NSMutableAttributedString(string: "")
let titleString = NSMutableAttributedString(string: self.title)
let space = NSMutableAttributedString(string: " ")
let imageAttachment = self.attachment
imageAttachment.image = image
finalString.append(imageAttachmentString)
finalString.append(space)
finalString.append(titleString)
self.titleLabel.attributedText = finalString
}
}
}
} else {
titleLabel.text = title
}

How to mask text of a UILabel or a UITextView?

I would like to mask text of a UILabel to achieve the following result
In Swift, You can do it like this:
var attributedString = NSMutableAttributedString(string: "Your String")
let textAttachment = NSTextAttachment()
textAttachment.image = UIImage(named: "Your Image Name")
let attrStringWithImage = NSAttributedString(attachment: textAttachment)
attributedString.insert(attrStringWithImage, at: 0)
label.attributedText = attributedString
This will work for you .
extension UILabel
{
func addImage(imageName: String)
{
let attachment:NSTextAttachment = NSTextAttachment()
attachment.image = UIImage(named: imageName)
let attachmentString:NSAttributedString = NSAttributedString(attachment: attachment)
let myString:NSMutableAttributedString = NSMutableAttributedString(string: self.text!)
myString.appendAttributedString(attachmentString)
self.attributedText = myString
}
}
Another version of the code that allow adding the icon before or after the label.
extension UILabel
{
func addImage(imageName: String, afterLabel bolAfterLabel: Bool = false)
{
let attachment: NSTextAttachment = NSTextAttachment()
attachment.image = UIImage(named: imageName)
let attachmentString: NSAttributedString = NSAttributedString(attachment: attachment)
if (bolAfterLabel)
{
let strLabelText: NSMutableAttributedString = NSMutableAttributedString(string: self.text!)
strLabelText.appendAttributedString(attachmentString)
self.attributedText = strLabelText
}
else
{
let strLabelText: NSAttributedString = NSAttributedString(string: self.text!)
let mutableAttachmentString: NSMutableAttributedString = NSMutableAttributedString(attributedString: attachmentString)
mutableAttachmentString.appendAttributedString(strLabelText)
self.attributedText = mutableAttachmentString
}
}
//you can remove the image by calling this function
func removeImage()
{
let text = self.text
self.attributedText = nil
self.text = text
}
}
For this use a UITextView instead of UILabel.
let imgRectBezier = UIBezierPath(rect: imgView.frame)
txtView.textContainer.exclusionPaths = [imgRectBezier]
Using this, the text will be excluded from the frame area added in the exclusion paths. You can even exclude multiple frames.

Get word that was tapped in UILabel instead of character [iOS]

I am trying to extract the word that was tapped in a UILabel using tap gesture recognizer. I am able to get the character that was tapped, as well as the range of the character, but I am not able to extract the full word. Is there a way to get the full string of the word that was tapped instead of the character?
if let label = descriptionLabel, tapGestureRecognizer != nil {
if label.attributedText == nil {
return
}
let storage = NSTextStorage(attributedString: label.attributedText ?? NSAttributedString())
let textContainer = NSTextContainer(size: label.bounds.size)
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
storage.addLayoutManager(layoutManager)
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = (label.lineBreakMode)
textContainer.maximumNumberOfLines = label.numberOfLines
let location: CGPoint = tapGestureRecognizer!.location(in: label)
let characterIndex: Int = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
if characterIndex < storage.length {
print("Character Index: \(Int(characterIndex))")
let range = NSRange(location: characterIndex, length: 1)
let substring: String? = (label.attributedText?.string as NSString?)?.substring(with: range)
//prints out one character, how do I get the full word of this character
print(substring)
}
}
Much appreciated
I don't like this way, but it works. Place the following after the print(substring) statement. Why doesn't UIKit have a method to extract the full string at a Range? Ridiculous.
let afterWords: [Any] = (afterString.components(separatedBy: " "))
let beforeWords: [Any] = (beforeString.components(separatedBy: ", "))
var attachmentString = ""
if let stringBeforeLocation = beforeWords.last as? String, let stringAfterLocation = afterWords.first as? String {
attachmentString = stringBeforeLocation
if stringAfterLocation.count > 0 {
let choppedString = (stringAfterLocation as NSString).substring(from: 1)
attachmentString = attachmentString + choppedString
print("FULL STRING TAPPED \(attachmentString)")
}
}

How to insert custom emoji in UILabel programmatically, in Swift?

I am building an iOS app where I have to add support for emojis within UILabel, so basically, whenever I receive a string containing either of these:
[kick-off]
[yellow-card]
[red-card]
[introduce]
[substitute]
[attention]
[free-kick]
[penalty]
[offside]
[extra-time]
[throw-in]
[corner]
[goal-post]
[bar]
[cheers]
[goal]
I have to replace these tags with a corresponding emoji. I have custom images for these emojis:
https://cdn-waf-beta.global.ssl.fastly.net/0.55.12/static/images/WAF_live_icons_sprite.png
Any idea how could I pull that off using Swift?
You need to create an NSTextAttachment and append it to an NSAttributedString.
Example:
let stringAttributes = [
// insert any attributes here
NSFontAttributeName : UIFont.systemFontOfSize(14)
,NSForegroundColorAttributeName : UIColor.blackColor()
]
let attributedString = NSMutableAttributedString(string: "the string before your image ", attributes: stringAttributes)
// Image needs to be NSData
let imageData:NSData = UIImagePNGRepresentation(yourImage)!
// Attachment type is very important
let attachment = NSTextAttachment(data: imageData, ofType: "public.png")
let attachmentString = NSAttributedString(attachment: attachment)
attributedString.appendAttributedString(attachmentString)
print(attributedString)
You can find the appropriate uti (Uniform Type Identifier) here
Here is the latest (swift v5) syntax with a few additions:
func strgWithImage(yourImage: String, preImage: String, postImage: String) -> NSMutableAttributedString {
// Call function with attributed text label:
// testLabel.attributedText = strgWithImage(yourImage: "doneX.png", preImage: "preS", postImage: "postS")
let stringAttributes = [
// insert any attributes here
NSAttributedString.Key.font : UIFont.systemFont(ofSize: 14)
,NSAttributedString.Key.foregroundColor : UIColor.black
]
let attributedString = NSMutableAttributedString(string: preImage, attributes: stringAttributes)
let postString = NSMutableAttributedString(string: postImage, attributes: stringAttributes)
let attachment = NSTextAttachment()
attachment.image = UIImage(named:yourImage) // loaded in assets
let imageOffsetY: CGFloat = -15.0
attachment.bounds = CGRect(x: 0, y: imageOffsetY, width: attachment.image!.size.width, height: attachment.image!.size.height)
let attachmentString = NSAttributedString(attachment: attachment)
attributedString.append(attachmentString)
attributedString.append(postString)
return attributedString
}

How to add image and text in UITextView in IOS?

I want to add both text and image in UITextView. The textview should be expanded according to the length of the text and image. In short what I want to do is that when I capture an image from camera or pick from gallery then it should display in UITextView and I should also be able to add some text with that image similar to Facebook.I am also attaching an image that how the UITextView will look like.
This is absolutely possible now, using
+ (NSAttributedString *)attributedStringWithAttachment:(NSTextAttachment *)attachment
See Apple docs here
And this example taken from this other answer:
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0,0,140,140)];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:#"before after"];
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIImage imageNamed:#"sample_image.jpg"];
CGFloat oldWidth = textAttachment.image.size.width;
//I'm subtracting 10px to make the image display nicely, accounting
//for the padding inside the textView
CGFloat scaleFactor = oldWidth / (textView.frame.size.width - 10);
textAttachment.image = [UIImage imageWithCGImage:textAttachment.image.CGImage scale:scaleFactor orientation:UIImageOrientationUp];
NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment];
[attributedString replaceCharactersInRange:NSMakeRange(6, 1) withAttributedString:attrStringWithImage];
textView.attributedText = attributedString;
Using the above code will get you an image with text inside a UITextView on iOS 7+. You can/show style the attributed text as you want it and probably set the width of the image to make sure it fits within your textView (as well as setting your own aspect ratio/scale preference)
Here's a quick test image:
Thank you for your code, it actually worked. I make a code in the Swift, so I would like to share Swift version of your code. I checked this code works too.
let textView = UITextView(frame: CGRectMake(50, 50, 200, 300))
let attributedString = NSMutableAttributedString(string: "before after")
let textAttachment = NSTextAttachment()
textAttachment.image = UIImage(named: "sample_image.jpg")!
let oldWidth = textAttachment.image!.size.width;
//I'm subtracting 10px to make the image display nicely, accounting
//for the padding inside the textView
let scaleFactor = oldWidth / (textView.frame.size.width - 10);
textAttachment.image = UIImage(CGImage: textAttachment.image!.CGImage, scale: scaleFactor, orientation: .Up)
var attrStringWithImage = NSAttributedString(attachment: textAttachment)
attributedString.replaceCharactersInRange(NSMakeRange(6, 1), withAttributedString: attrStringWithImage)
textView.attributedText = attributedString;
self.view.addSubview(textView)
Code For Swift 3.0
var attributedString :NSMutableAttributedString!
attributedString = NSMutableAttributedString(attributedString:txtBody.attributedText)
let textAttachment = NSTextAttachment()
textAttachment.image = image
let oldWidth = textAttachment.image!.size.width;
//I'm subtracting 10px to make the image display nicely, accounting
//for the padding inside the textView
let scaleFactor = oldWidth / (txtBody.frame.size.width - 10);
textAttachment.image = UIImage(cgImage: textAttachment.image!.cgImage!, scale: scaleFactor, orientation: .up)
let attrStringWithImage = NSAttributedString(attachment: textAttachment)
attributedString.append(attrStringWithImage)
txtBody.attributedText = attributedString;
if you just want place the image in the end, you can use
//create your UIImage
let image = UIImage(named: change_arr[indexPath.row]);
//create and NSTextAttachment and add your image to it.
let attachment = NSTextAttachment()
attachment.image = image
//put your NSTextAttachment into and attributedString
let attString = NSAttributedString(attachment: attachment)
//add this attributed string to the current position.
textView.textStorage.insertAttributedString(attString, atIndex: textView.selectedRange.location)
Check This answer
if you want to get the image from the camera, you can try my code below: (Swift 3.0)
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
//create and NSTextAttachment and add your image to it.
let attachment = NSTextAttachment()
attachment.image = image
//calculate new size. (-20 because I want to have a litle space on the right of picture)
let newImageWidth = (textView.bounds.size.width - 20 )
let scale = newImageWidth/image.size.width
let newImageHeight = image.size.height * scale
//resize this
attachment.bounds = CGRect.init(x: 0, y: 0, width: newImageWidth, height: newImageHeight)
//put your NSTextAttachment into and attributedString
let attString = NSAttributedString(attachment: attachment)
//add this attributed string to the current position.
textView.textStorage.insert(attString, at: textView.selectedRange.location)
picker.dismiss(animated: true, completion: nil)
}
Lots of people are making this a lot more complicated than it needs to be. Firstly, add your image to the catalogue at the right size:
Then, create the NSAttributedString in code:
// Creates the logo image
let twitterLogoImage = NSTextAttachment()
twitterLogoImage.image = UIImage(named: "TwitterLogo")
let twitterLogo = NSAttributedString(attachment: twitterLogoImage)
Then add the NSAttributedString to what you want:
// Create the mutable attributed string
let text = NSMutableAttributedString(string: "")
/* Code above, creating the logo */
/* More attributed strings */
// Add the logo to the whole text
text.append(twitterLogo)
textView.attributedText = text
Or:
textView.attributedText = twitterLogo
let htmlString = "<html><body><h1>This is the title</h1><p>This is the first paragraph.</p><img src=\"https://miro.medium.com/max/9216/1*QzxcfBpKn5oNM09-vxG_Tw.jpeg\" width=\"360\" height=\"240\"><p>This is the second paragraph.</p><p>This is the third paragraph.</p><p>This is the fourth paragraph.</p><p>This is the last paragraph.</p></body></html>"
Use this string extension:
extension String {
func convertToAttributedFromHTML() -> NSAttributedString? {
var attributedText: NSAttributedString?
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue]
if let data = data(using: .unicode, allowLossyConversion: true), let attrStr = try? NSAttributedString(data: data, options: options, documentAttributes: nil) {
attributedText = attrStr
}
return attributedText
}
}
Then set the textView:
textView.attributedText = htmlString.convertToAttributedFromHTML()
If you want to add a simple image in textview from Gallery or Camera as an attachment then this method should be used:
func insertImage(_ image:UIImage) {
let attachment = NSTextAttachment()
attachment.image = image
attachment.setImageHeight(height: 200)
let attString = NSAttributedString(attachment: attachment)
/// at is current cursor position
self.descriptionTextView.textStorage.insert(attString, at: self.descriptionTextView.selectedRange.location)
descriptionTextView.font = UIFont(name: UIFont.avenirNextRegular, size: 17)
descriptionTextView.textColor = .white
}
If you want to add an image from a link than you need to do this, there can be multiple links in string, so will be achive using these methods.
Checks URLS from exisiting string so that we can download an image and show as attachment
func checkForUrls(text: String) -> [NSTextCheckingResult] {
let types: NSTextCheckingResult.CheckingType = .link
do {
let detector = try NSDataDetector(types: types.rawValue)
let matches = detector.matches(in: text, options: .reportCompletion, range: NSRange(location: 0, length: text.count))
return matches
// return matches.compactMap({$0.url})
} catch let error {
debugPrint(error.localizedDescription)
}
return []
}
// Recursive funtion call next after successfull downloaded
func convertToAttachment() {
if imageURLResultsFromStr.count > 0 {
imageTOAttributedText(imageURLResultsFromStr.first?.url, imageURLResultsFromStr.first?.range)
}
}
**// MARK: Server URL to Image conversion to show**
func imageTOAttributedText(_ url:URL?,_ range:NSRange?) {
guard let url = url, let range = range else { return }
let imgView = UIImageView()
imgView.kf.setImage(with: url, completionHandler: { result in
switch result {
case .success(var data):
let attachment = NSTextAttachment()
data.image.accessibilityIdentifier = self.recentlyUploadedImage
attachment.image = data.image
// attachment.fileType = self.recentlyUploadedImage
attachment.setImageHeight(height: 200)
/// This will help to remove existing url from server which we have sent as url
/// Start
let mutStr = self.descriptionTextView.attributedText.mutableCopy() as! NSMutableAttributedString
let range = (mutStr.string as NSString).range(of: "\n\(url.absoluteString)\n")
mutStr.deleteCharacters(in: range)
self.descriptionTextView.attributedText = mutStr
//End
/// Add image as attachment downloaded from url
let attString = NSAttributedString(attachment: attachment)
self.descriptionTextView.textStorage.insert(attString, at: range.location)
/// Recursivly calls to check how many urls we have in string to avoid wrong location insertion
/// We need to re-calculate new string from server after removing url string and add image as attachment
self.imageURLResultsFromStr.remove(at: 0)
self.imageURLResultsFromStr = self.checkForUrls(text: self.descriptionTextView.text)
self.convertToAttachment()
case .failure(let error):
print(error)
}
})
}
// Now call this function and initialise it from server string, Call it from viewdidload or from api response
func initialise() {
self.descriptionTextView.text = ..string from server
self.imageURLResultsFromStr = self.checkForUrls(text:string from server)
convertToAttachment()
}
var imageURLResultsFromStr:[NSTextCheckingResult] = []
NSURL *aURL = [NSURL URLWithString:[[NSString stringWithFormat:#"%#%#",Image_BASE_URL,str] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
//UIImage *aImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:aURL]];
//[aImage drawInRect:CGRectMake(0, 0, 20, 20)];
__block NSTextAttachment *imageAttachment = [NSTextAttachment new];
imageAttachment.bounds = CGRectMake(0, -5, 20, 20);
NSAttributedString *stringWithImage = [NSAttributedString attributedStringWithAttachment:imageAttachment];
[deCodedString replaceCharactersInRange:NSMakeRange(deCodedString.length, 0) withAttributedString:stringWithImage];
incomingMessage.messageAttributedString = deCodedString;
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
imageAttachment.image = [UIImage imageNamed:#"profile_main_placeholder"];
[downloader downloadImageWithURL:aURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (image && finished) {
[image drawInRect:CGRectMake(0, 0, 20, 20)];
imageAttachment.image = image;
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[self.tbl_Conversation reloadRowsAtIndexPaths:[self.tbl_Conversation indexPathsForVisibleRows]
withRowAnimation:UITableViewRowAnimationNone];
[self.tbl_Conversation reloadData];
});
// NSAttributedString *stringWithImage = [NSAttributedString attributedStringWithAttachment:imageAttachment];
// [deCodedString replaceCharactersInRange:NSMakeRange(deCodedString.length, 0) withAttributedString:stringWithImage];
// incomingMessage.messageAttributedString = deCodedString;
}
}];
Please, try use placeholderTextView to simple input with icon placeholder support.
#IBOutlet weak var tvMessage: PlaceholderTextView!
let icon: NSTextAttachment = NSTextAttachment()
icon.image = UIImage(named: "paper-plane")
let iconString = NSMutableAttributedString(attributedString: NSAttributedString(attachment: icon))
tvMessage.icon = icon
let textColor = UIColor.gray
let lightFont = UIFont(name: "Helvetica-Light", size: tvMessage.font!.pointSize)
let italicFont = UIFont(name: "Helvetica-LightOblique", size: tvMessage.font!.pointSize)
let message = NSAttributedString(string: " " + "Personal Message", attributes: [ NSFontAttributeName: lightFont!, NSForegroundColorAttributeName: textColor])
iconString.append(message)
let option = NSAttributedString(string: " " + "Optional", attributes: [ NSFontAttributeName: italicFont!, NSForegroundColorAttributeName: textColor])
iconString.append(option)
tvMessage.attributedPlaceHolder = iconString
tvMessage.layoutSubviews()
You can refer to how MLLabel work.
Main ideal is NSTextAttachment
Create ImageAttachment extends NSTextAttachment -> override - (nullable UIImage *)imageForBounds:(CGRect)imageBounds textContainer:(nullable NSTextContainer *)textContainer characterIndex:(NSUInteger)charIndex to return image size like you want.
Create NSAttributedString with [NSAttributedString attributedStringWithAttachment:ImageAttachment]
Create NSMutableAttributedString and append attributed string of ImageAttachment using - (void)replaceCharactersInRange:(NSRange)range withAttributedString:(NSAttributedString *)attrString;
Result: You have NSMutableAttributedString contain your image and set it to textView.attributedText
Sample: HERE

Resources