1

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.

Community
  • 1
  • 1
  • In the inner for loop in your second listing, is mention.nsrange greater than the range of tweetTextLabel.attributedText? If it is, that would cause the range exception. – Swift Dev Journal Oct 10 '16 at 00:21
  • mention.nsrange is a part of tweetTextLabel.attributedText?. That's why it can't be greater. I edited the partial code to the whole code to give a better Idea. – Jaiden Ryder Oct 10 '16 at 19:58
  • Are you sure the ranges are the same? The range exception is telling you they are not. Set a breakpoint inside the inner for loop and look at the ranges in the debugger. – Swift Dev Journal Oct 10 '16 at 20:24
  • @MarkSzymczyk I put a break point inside inner for loop. I calculated the range of **tweetTextLabel** with the method **NSRange(location: 0, length: tweetTextLabel?.attributedText!.length)**. In some tweets the range is OK, however in some cases both location and length takes some weird astronomical values. Nonetheless when I put a print statement inside the inner for loop all the values seem OK. mention.nsrange seems inside the range of tweetTextLabel – Jaiden Ryder Oct 12 '16 at 06:09

0 Answers0