I am following Stanford's cs193P "Developing IOS 9 Apps with swift" course. I'm working on assignment-4 of the lecture. The app fetches the tweets, which include the word you search, from twitter. I try to change the colour of words which start with special characters(@,# or http).
When I compile the app it succeeds to change colours and the app runs without problem. But then when I scroll down and my app tries to show new tweets it crashes giving the following error.
Terminating app due to uncaught exception 'NSRangeException', reason: 'NSMutableRLEArray objectAtIndex:effectiveRange:: Out of bounds'
I am using Xcode 7.3.1 with swift 2.2
I searched both on Stackoverflow and google and found the following similar questions Question-1 Question-2 Question-3 Question-4
In the questions above problems were related to the length of the range or misusing the NSRange related to its length.
In my case I don't see any problem related to length. You can click to view my whole project in Github
public class Mention: NSObject
{
public var keyword: String // will include # or @ or http prefix
public var nsrange: NSRange // index into an NS[Attributed]String made from the Tweet's text
public override var description: String { return "\(keyword) (\(nsrange.location), \(nsrange.location+nsrange.length-1))" }
init?(fromTwitterData data: NSDictionary?, inText text: NSString, withPrefix prefix: String)
{
guard
let indices = data?.valueForKeyPath(Tweet.TwitterKey.Entities.Indices) as? NSArray,
let start = (indices.firstObject as? NSNumber)?.integerValue where start >= 0,
let end = (indices.lastObject as? NSNumber)?.integerValue where end > start
else {
return nil
}
var prefixAloneOrPrefixedMention = prefix
if let mention = data?.valueForKeyPath(Tweet.TwitterKey.Entities.Text) as? String {
prefixAloneOrPrefixedMention = mention.prependPrefixIfAbsent(prefix)
}
let expectedRange = NSRange(location: start, length: end - start)
guard
let nsrange = text.rangeOfSubstringWithPrefix(prefixAloneOrPrefixedMention, expectedRange: expectedRange)
else {
return nil
}
self.keyword = text.substringWithRange(nsrange)
self.nsrange = nsrange
} }
As can be seen start is always bigger than end. That's why in expectedRange length(which is end-start) can never be minus.
And finally my code which causes the crash is below(The line starting with "tweetTextLabel?.attributedText" causes the crash)
class TweetTableViewCell: UITableViewCell
{
@IBOutlet weak var tweetScreenNameLabel: UILabel!
@IBOutlet weak var tweetTextLabel: UILabel!
@IBOutlet weak var tweetProfileImageView: UIImageView!
@IBOutlet weak var tweetCreatedLabel: UILabel!
var tweet: Twitter.Tweet? {
didSet {
updateUI()
}
}
var mentionsList = [Array<Mention>]()
private func updateUI()
{
// reset any existing tweet information
tweetTextLabel?.attributedText = nil
tweetScreenNameLabel?.text = nil
tweetProfileImageView?.image = nil
tweetCreatedLabel?.text = nil
// load new information from our tweet (if any)
if let tweet = self.tweet
{
tweetTextLabel?.text = tweet.text
if tweetTextLabel?.text != nil {
for _ in tweet.media {
tweetTextLabel.text! += " "
}
}
mentionsList.append(tweet.hashtags)
mentionsList.append(tweet.urls)
mentionsList.append(tweet.userMentions)
var mentionColor = UIColor.clearColor()
for mentions in mentionsList {
switch mentions {
case _ where mentions == tweet.hashtags:
mentionColor = UIColor.blueColor()
case _ where mentions == tweet.urls:
mentionColor = UIColor.redColor()
case _ where mentions == tweet.userMentions:
mentionColor = UIColor.brownColor()
default: break
}
for mention in mentions {
(tweetTextLabel?.attributedText as? NSMutableAttributedString)?.addAttribute(
NSForegroundColorAttributeName, value: mentionColor, range: mention.nsrange) // Causes the crash
}
}
tweetScreenNameLabel?.text = "\(tweet.user)" // tweet.user.description
if let profileImageURL = tweet.user.profileImageURL {
if let imageData = NSData(contentsOfURL: profileImageURL) { // blocks main thread!
tweetProfileImageView?.image = UIImage(data: imageData)
}
}
let formatter = NSDateFormatter()
if NSDate().timeIntervalSinceDate(tweet.created) > 24*60*60 {
formatter.dateStyle = NSDateFormatterStyle.ShortStyle
} else {
formatter.timeStyle = NSDateFormatterStyle.ShortStyle
}
tweetCreatedLabel?.text = formatter.stringFromDate(tweet.created)
}
}
}
Please help me. I am already working on this issue for several days and cannot find where the mistake is. Maybe because of some special characters the number of characters in string are more. But I don't know how to figure it out.