Search code examples
iosregexswiftnsattributedstringnsrange

Attribute the word within a specific range using NSRegularExpression in Swift


I have some pure Html string and some of them has title tags <b>Title</b>.

facilities: "<b>Facilities</b><br/>24-hour security, Barbecue area, Car park, Clubhouse, Function room, Gym, Outdoor swimming pool, Playground, Swimming pool<br/><br/><b>Rooms</b><br/>Dining room, Ensuites, Living room, Maid\'s room, Utility room<br/><br/><b>Outdoor</b><br/>Balcony<br/><br/><b>View</b><br/>City, Open<br/><br/><b>Direction</b><br/>South East"

So i use NSRegularExpression pattern to extract titles from string and store in an array of strings. And later, i make these titles bold (attributed string) and display. So this is how i do that:

var titlesArray = [String]()
let regex = try! NSRegularExpression(pattern: "<b>(.*?)</b>", options: [])
let basicDescription = facilities as NSString

regex.enumerateMatchesInString(facilities, options: [], range: NSMakeRange(0, facilities.characters.count)) { result, flags, stop in
  if let range = result?.rangeAtIndex(1) {
    titlesArray.append(basicDescription.substringWithRange(range))
  }
}

let convertedDescription = facilities.html2String as NSString
let attributedString = NSMutableAttributedString(string: convertedDescription as String, attributes: [NSFontAttributeName:UIFont.systemFontOfSize(14.0)])
let boldFontAttribute = [NSFontAttributeName: UIFont.boldSystemFontOfSize(15.0)]

if titlesArray.count > 0 {
   for i in 0..<titlesArray.count {
   attributedString.addAttributes(boldFontAttribute, range: convertedDescription.rangeOfString(titlesArray[i]))
   } 
}

So, everything is alright. But the problem is, sometimes i receive Html tagged strings which has duplicate words where one of them is title with title tag, another one is just a simple word which i do not need to bold. But this function will look for that word and bold it inside for loop and ignore the real title which comes after the simple word.

This is what i get:

enter image description here

So here, how can i ignore the first "Outdoor" and bold the second one which i want. Thank you for any help.


Solution

  • In Objective-C, shouldn't be too hard to translate in Swift (since it seems you already know some of the methods).

    attr1 is rendered with init(data:, options:, documentAttributes:). I didn't add any other effects (like preferred size for bold/normal, color, you just have to enumerate on it and change the effects) attr2 is rendered more the way you wanted with your regex. It just doesn't take in account all tags, just the bold, and I hardly coded the replacement for new lines (<br/> into \n). But that could be something to use. I didn't do more tests on your regex (the while loop could be stucked?)

    NSString *str = @"<b>Facilities</b><br/>24-hour security, Barbecue area, Car park, Clubhouse, Function room, Gym, Outdoor swimming pool, Playground, Swimming pool<br/><br/><b>Rooms</b><br/>Dining room, Ensuites, Living room, Maid\'s room, Utility room<br/><br/><b>Outdoor</b><br/>Balcony<br/><br/><b>View</b><br/>City, Open<br/><br/><b>Direction</b><br/>South East";
    
    NSError *errorAttr1 = nil;
    NSAttributedString *attr1 = [[NSAttributedString alloc] initWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:@{NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType} documentAttributes:nil error:&errorAttr1];
    if (errorAttr1)
    {
        NSLog(@"Error AttributedStr Conversion with initWithData:options:documentsAttributes:error: %@", errorAttr1);
    }
    else
    {
        NSLog(@"attr1: %@", attr1);
        [_tv1 setAttributedText:attr1];
    
    }
    
    str = [str stringByReplacingOccurrencesOfString:@"<br/>" withString:@"\n"];
    
    NSError *errorRegex = nil;
    NSString *openingTag = @"<b>";
    NSString *closingTag = @"</b>";
    NSString *pattern = [NSString stringWithFormat:@"%@(.*?)%@", openingTag, closingTag];
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&errorRegex];
    if (errorRegex)
    {
        NSLog(@"Error regex: %@", errorRegex);
        return;
    }
    
    
    NSDictionary *boldAttributes = @{NSForegroundColorAttributeName:[UIColor darkGrayColor],
                                     NSFontAttributeName:[UIFont boldSystemFontOfSize:15]};
    NSDictionary *normalAttributes = @{NSForegroundColorAttributeName:[UIColor darkGrayColor],
                                       NSFontAttributeName:[UIFont systemFontOfSize:14]};
    
    NSMutableAttributedString *attr2 = [[NSMutableAttributedString alloc] initWithString:str attributes:normalAttributes]; //Add the initial attributes there
    
    //Now we'll add the specific attribues
    NSTextCheckingResult *match = [regex firstMatchInString:[attr2 string] options:0 range:NSMakeRange(0, [attr2 length])];
    while (match)
    {
        NSRange range = [match range];
        NSString *foundStr = [[attr2 string] substringWithRange:range];
        NSAttributedString *temp = [[NSAttributedString alloc] initWithString:[foundStr substringWithRange:NSMakeRange([openingTag length], [foundStr length]-[openingTag length]-[closingTag length])] attributes:boldAttributes];
        [attr2 replaceCharactersInRange:range withAttributedString:temp];
        match = [regex firstMatchInString:[attr2 string] options:0 range:NSMakeRange(0, [attr2 length])];
    }
    NSLog(@"attr2: %@", attr2);
    [_tv2 setAttributedText:attr2];
    

    _tv1 and _tv2 are two UITextView (IBOulet). It rendered: (_tv1 is the one on top, _tv2 is the second one).

    enter image description here