Find Attributes from Attributed String That User Typed

Find attributes from attributed string that user typed

You can enumerate the Attributes with this code (whole range).

[attrStr enumerateAttributesInRange:NSMakeRange(0, [attrStr length]) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:
^(NSDictionary *attributes, NSRange range, BOOL *stop) {
//Do something here
}
}];

For specific range, your have to change the code NSMakeRange(0, [attrStr lenght]) by the range you need.

After your edit, your specific need, this code should do the work:

[attrStr enumerateAttributesInRange:NSMakeRange(0, [attrStr length])
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:
^(NSDictionary *attributes, NSRange range, BOOL *stop)
{
NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
UIFont *currentFont = [mutableAttributes objectForKey: NSFontAttributeName];
UIFont *newFont;
if ([[currentFont fontName] isEqualToString:@"Calibri-BoldItalic"])
newFont = [UIFont fontWithName:@"HelveticaNeue-BoldItalic" size:currentFont.pointSize];
else if ([[currentFont fontName] isEqualToString:@"Calibri-Bold"])
newFont = [UIFont fontWithName:@"HelveticaNeue-Bold" size:currentFont.pointSize];
else if ([[currentFont fontName] isEqualToString:@"Calibri-Italic"])
newFont = [UIFont fontWithName:@"HelveticaNeue-Italic" size:currentFont.pointSize];
[mutableAttributes setObject:newFont forKey:@"NSFont"];
[attrStr setAttributes:mutableAttributes range:range];
}];

From your comments, you may see more complicated things, so I will give you some ideas/tips to do/check. You'll just have to complete my previous code to manage all cases.

• Case: There is no font.

You just have to check if currentFont is nil. If yes, you set what you want. Since there is no attribute for italic/bold (since they are "included" in the font), I suggest you set for newFont: HelveticaNeue.

• Case: It's another font than Calibri. There it's quite more complicated.

I've worked with that, and here what I got:
Font name (which is the postscript name) is made like this for HelveticaNeue (as given example):

HelveticaNeue

HelveticaNeue-Light

HelveticaNeue-UltraLight

HelveticaNeue-Medium

HelveticaNeue-LightItalic

HelveticaNeue-Italic

HelveticaNeue-UltraLightItalic

HelveticaNeue-Bold

HelveticaNeue-BoldItalic

HelveticaNeue-CondensedBold

HelveticaNeue-CondensedBlack

So the first thing you could think of: I just have to check the suffix of the name (with hasSuffix:). But that may go wrong.

Indeed, some fonts have a foundry suffix (the ones I found are LT, MT, and EF).

Well, for example:

Times New Roman "real name" is TimesNewRomanPSMT.

And its italic version is TimesNewRomanPS-ItalicMT
That's why we have to check if it has a foundry suffix because TimesNewRomanPSMT-Italic won't work (since it doesn't exist, if you try to set a font with this name, it will be nil).

So, as far as I know, and as far I may have some experience and the case I've seen, what you could do:
Check if the [currentFont name] contains either: BoldItalic, Italic or Bold, using a NSRegularExpression or a rangeOfString:. Note that I'd suggest to check before for BoldItalic since an Italic may "hides" a Bold just before it, and Bold may "hides" an Italic just after it.

In order to do so, I'd suggest that you create a method like this (or of this style):

-(UIFont)getCorrespondingHelveticaNeueFontMatchingCharacteriticsOf:(UIFont *)font
{
UIFont *helveticaNueue;
if (!font) //Checking if there is a font)
{
helveticaNueue = [UIFont fontWithName@"HelveticaNueue" size:defaultSizeYouSet];
}
else
{
//Check if it's BoldItalic/Italic/Bold
}
return helveticaNueue;
}

checking all the case I mentioned before.

So, from the previous code, you'll just have to replace the if tests that were working specificly for Calibri with:

UIFont *newFont = [self getCorrespondingHelveticaNeueFontMatchingCharacteriticsOf:currentFont];

Now a third part you may want to do (and add them to your test):
Is the Black case of a font want to be managed as Bold? Well, Helvetica Neue has this "property", but you may want to use directly the black property for Helvetica Neue, and may want to check if currentFont has it (and again, another case).

EDIT:
You can use since iOS7 we can use UIFontDescriptor. Here is what you could do:

-(UIFont *)customFontMatchingFont:(UIFont *)otherFont
{
NSString *customFontName = @"Helvetica"; //The custom font
UIFont *tempFont = [UIFont fontWithName:customFontName size:[otherFont pointSize]];
return [UIFont fontWithDescriptor:[[tempFont fontDescriptor] fontDescriptorWithSymbolicTraits:[[otherFont fontDescriptor] symbolicTraits]] size:[otherFont pointSize]];
}

Note: I didn't check all the cases (what if your custom family doesn't have a matching font, what does it return? etc.), but that could be a nice add-on if you don't do too complicated things.

Is it possible to get a listing of attributes and ranges for an NSMutableAttributedString?

Apple expects you to use enumerateAttributesInRange:options:usingBlock:. The block you supply will receive ranges and the attributes applicable for that range.

I've used that in my code to create invisible buttons that are placed behind text so that it acts as a hyperlink.

You could also use enumerateAttribute:inRange:options:usingBlock: if there's only one you're interested in, but no halfway house is provided where you might be interested in, say, two attributes but not every attribute.

Get all attributes of a NSMutableAttributedString into a dictionary

Convert to html

var s: NSAttributedString? = "Pizza stirng"
var documentAttributes: [AnyHashable: Any] = [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType]
var htmlData: Data? = try? s?.data(from: NSRange(location: 0, length: s?.length), documentAttributes: documentAttributes as? [String : Any] ?? [String : Any]())
var htmlString = String(data: htmlData, encoding: String.Encoding.utf8)

and try this to get back NSAttributedString

 var atributedStr = try? NSAttributedString(data: htmlString.data(using: String.Encoding.utf8), options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: (String.Encoding.utf8)], documentAttributes: nil)

Swift 3: Getting attributes at substring in NSAttributedString

Here's how you can do it without hardcoding. This is Swift 3 playground code based on your sample:

import UIKit
import PlaygroundSupport

let linkStr = "Click <a href='http://google.com'>here</a> for good times."
let attributedText = try! NSAttributedString(
data: linkStr.data(using: String.Encoding.unicode, allowLossyConversion: true)!,
options: [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
documentAttributes: nil)

attributedText.enumerateAttribute(NSAttributedString.Key.link, in: NSMakeRange(0, attributedText.length), options: [.longestEffectiveRangeNotRequired]) { value, range, isStop in
if let value = value {
print("\(value) found at \(range.location)")
}
}

The print statement outputs:

http://google.com/ found at 6

p.s. 'NSAttributedString.Key.link' is instead of 'NSLinkAttributeName' because of the renaming.

Swift 4 attributedString get typing attributes

You can map the [String: Any] dictionary to a
[NSAttributedStringKey: Any] dictionary with

let typingAttributes = Dictionary(uniqueKeysWithValues: self.textView.typingAttributes.map {
key, value in (NSAttributedStringKey(key), value)
})

let text = NSAttributedString(string: "test123", attributes: typingAttributes)

Here is a possible extension method for that purpose, it is
restricted to dictionaries with string keys:

extension Dictionary where Key == String {

func toAttributedStringKeys() -> [NSAttributedStringKey: Value] {
return Dictionary<NSAttributedStringKey, Value>(uniqueKeysWithValues: map {
key, value in (NSAttributedStringKey(key), value)
})
}
}

How to search word in a attributed text in swift 3?

Issue:

let attributedString = NSMutableAttributedString(string: targetString)

You are creating a NSAttributedString with your HTML string without parsing. So you see the HTML tags.

You have already your own method that parse a HTML string into a NSAttributedString, use it (and keep in mind we need a mutable one):

let attributedString = NSMutableAttributedString(attributedString: targetString.htmlAttributedString(fontSize: 14.0))

Now, the NSAttributedString conversion removed the HTML tags (and interprets them if possible, because NSAttributedString doesn't interprets ALL HTML tags, only a few ones). So the length, the ranges are all different.

So you can't do this anymore:

let range = NSRange(location: 0, length: targetString.utf16.count)

You need to update it to:

let range = NSRange(location: 0, length: attributedString.string.utf16.count)

Same here:

for match in regex.matches(in: targetString, options: .withTransparentBounds, range: range) {

To be updated to:

for match in regex.matches(in: attributedString.string, options: .withTransparentBounds, range: range) {

Get the number of strings that have a url attribute in an attributed string

You’re close. For this you can use the enumerateAttribute(_:in:options:using:) method. You pass in the attribute you are interested in (in your case .link) and it calls the closure with each subrange of the string with the range and the value of the attribute. This includes subranges that don’t have that attribute. In this case nil is passed for the value. So to count the links in your string you count the non-nil attributes:

func linksIn(_ attributedString: NSAttributedString) -> Int {
var count = 0
attributedString.enumerateAttribute(.link, in: NSRange(location: 0, length: attributedString.length), options: []) { attribute, _, _ in
if attribute != nil {
count += 1
}
}
return count
}


Related Topics



Leave a reply



Submit