Search code examples
iosuitableviewnsuserdefaultsnskeyedarchivernskeyedunarchiver

App crashes after reopening


Basically every time I close and reopen app it crashes when I open History tab. Im pretty sure its because of how i use NSUserDefaults. Can anyone guide me through?

What I noticed is that sometimes it opens History tab after reopening but if i add a new entry to the Nsuserdefaults it will crash, after I reopen it it will work fine and will show the previous entry or it will remove everything and keep the new entry only.

    //
    //  SecondViewController.swift
    //  Angelina
    //
    //  Created by Artiom Sobol on 1/3/16.
    //  Copyright © 2016 Artiom Sobol. All rights reserved.
    //

    import UIKit

    class History: UIViewController, UITableViewDataSource, UITableViewDelegate
    {
        // test variable
        var test: MyHistory!
        // array to store unarchived history
        var newHistory = [MyHistory]()

        //outlet for tableview

        let defaults = NSUserDefaults.standardUserDefaults()

        @IBOutlet var tableView: UITableView!

        override func viewDidLoad()
        {

            //change the background
            self.view.backgroundColor = UIColor(patternImage: UIImage(named: "newBackground.jpg")!)
            super.viewDidLoad()


            if let savedPeople = defaults.objectForKey("MyHistory") as? NSData {
                newHistory = NSKeyedUnarchiver.unarchiveObjectWithData(savedPeople) as! [MyHistory]
            }
            tableView.delegate = self
            tableView.dataSource = self
        }

        override func viewDidAppear(animated: Bool) {
            super.viewDidAppear(animated)
            tableView.reloadData()
        }


        func tableView(tableView: UITableView,numberOfRowsInSection section: Int) -> Int
        {
            return self.newHistory.count
        }

        func numberOfSectionsInTableView(tableView: UITableView) -> Int
        {
            return 1
        }

        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
        {

            let cell = tableView.dequeueReusableCellWithIdentifier("historyCell", forIndexPath: indexPath) as! historyCell
            let person = newHistory[indexPath.item]
          //  let defaults2 = NSUserDefaults.standardUserDefaults()

            print("This is count", newHistory.count)

          //  if let savedPeople = defaults.objectForKey("MyHistory") as? NSData {
        //        newHistory = //NSKeyedUnarchiver.unarchiveObjectWithData(savedPeople) as! [MyHistory]
//            }



           // cell.durationLabel.text = String(person.durationNumber)
            let (hour,minutes,seconds) = secondsToHoursMinutesSeconds(person.durationNumber)

            if(seconds < 10 && minutes < 10)
            {
                cell.durationLabel.text = "0\(hour):0\(minutes):0\(seconds)"
            }
            else if(seconds > 9 && minutes < 10)
            {
                cell.durationLabel.text = "0\(hour):0\(minutes):\(seconds)"
            }
            else if(seconds > 9 && minutes > 9)
            {
                cell.durationLabel.text = "0\(hour):\(minutes):\(seconds)"
            }
            else if(seconds < 10 && minutes > 9)
            {
                cell.durationLabel.text = "0\(hour):\(minutes):0\(seconds)"
            }


            cell.kicksLabel.text = String(person.kicksNumber)

            return cell
        }


        func secondsToHoursMinutesSeconds (seconds : Int) -> (Int, Int, Int)
        {
            return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
        }


    }

I think i just realized that my array gets restarted after the app is turned off. thats why the table crashes cus it goes out of bounds..

this is my other file where the saving of the array happens.

import UIKit
class Kicks: UIViewController
{
  var count = 0 as Int
    var countKicks = 0 as Int
    var kickReached = false as Bool
    var pressedOnce = true as Bool
    var timer = NSTimer()
    var test: MyHistory!
    var dateString = " "

    @IBOutlet var timerLabel: UITextField!
    @IBOutlet var kicksLabel: UITextField!

    @IBOutlet var dateDisplay: UITextField!

    @IBOutlet var kickbutton: UIButton!

    var myHistoryArray: [MyHistory] = []
    var currentMyHistory: MyHistory!

    override func viewDidLoad()
    {
        super.viewDidLoad()
        self.printTimestamp()
        // Do any additional setup after loading the view, typically from a nib.
        self.view.backgroundColor = UIColor(patternImage: UIImage(named: "background13.png")!)
    }

    override func didReceiveMemoryWarning()
    {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

      @IBAction func kickButton()
    {

        //currentMyHistory.kicksNumber = 5
        kickbutton.setImage(UIImage(named: "gold-button-heart-icon.png"), forState: UIControlState.Normal)


        if(pressedOnce == true)
        {
            pressedOnce = false
            timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("counter"), userInfo: nil, repeats: true)
        }
        else if(kickReached == true)
        {

            //  let date = NSDate()
            //  let calendar = NSCalendar.currentCalendar()
            // let timer_total = calendar.components([ .Hour, .Minute, .Second], fromDate: date)

        }
        else if(pressedOnce == false)
        {
            countKicks++
            kicksLabel.text = "\(countKicks)"
            if(countKicks == 10)
            {
                self.printTimestamp()
                kickReached = true
                timer.invalidate()
                kickbutton.setImage(UIImage(named: "pink-button-heart-icon.png"), forState: UIControlState.Normal)
                congratsAlert()

                currentMyHistory = MyHistory(kicksNumber: countKicks, durationNumber: count)

                myHistoryArray.append(currentMyHistory)


                test = myHistoryArray[0]

                print("countof array ",  myHistoryArray.count)

                printTimestamp() // Prints "Sep 9, 2014, 4:30 AM"

                //save data
                let savedData = NSKeyedArchiver.archivedDataWithRootObject(myHistoryArray)
                let defaults = NSUserDefaults.standardUserDefaults()
                defaults.setObject(savedData, forKey: "MyHistory")
                clear()

            }
        }

    }

    func printTimestamp() {
        let timestamp = NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: .MediumStyle, timeStyle: .ShortStyle)
        print(timestamp)
        dateDisplay.text = "\(timestamp)"
    }



    // save countKicks, count, and stamp i
    func congratsAlert()
    {
        let alert = UIAlertController(title: "Congratulation", message: "Yay!!! Angelina kicked 10 times in less than 2 hours.",preferredStyle: .Alert)

        let okAction = UIAlertAction(title: "Ok",style: .Default,handler:{(action:UIAlertAction) -> Void in})
        alert.addAction(okAction)

        presentViewController(alert,animated: true,completion: nil)
    }

    func clear()
    {
        count = 0
        countKicks = 0
        kickReached = false
        pressedOnce = true
        timerLabel.text = "00:00:0\(count)"
        kicksLabel.text = "\(countKicks)"
    }

    func counter()
    {
        ++count
        let (hour,minutes,seconds) = secondsToHoursMinutesSeconds(count)

        if(seconds < 10 && minutes < 10)
        {
            timerLabel.text = "0\(hour):0\(minutes):0\(seconds)"
        }
        else if(seconds > 9 && minutes < 10)
        {
            timerLabel.text = "0\(hour):0\(minutes):\(seconds)"
        }
        else if(seconds > 9 && minutes > 9)
        {
            timerLabel.text = "0\(hour):\(minutes):\(seconds)"
        }
        else if(seconds < 10 && minutes > 9)
        {
            timerLabel.text = "0\(hour):\(minutes):0\(seconds)"
        }
    }

    func secondsToHoursMinutesSeconds (seconds : Int) -> (Int, Int, Int)
    {
        return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
    }

}

Solution

  • It says clearly:

    'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'

    Double check your array objects like newHistory has all the elements when you app starts

    It seems

        func tableView(tableView: UITableView,numberOfRowsInSection section: Int) -> Int
        {
            return self.newHistory.count
        }
    

    self.newHistory.count is not 0 so it will trigger cellForRowAtIndexPath, try add two break points inside numberOfRowsInSection and cellForRowAtIndexPath, so you can know what's goes wrong. Usually, if no data, numberOfRowsInSection will return 0 and cellForRowAtIndexPath will not be triggered.

    You can also an exception point In Xcode BreakPoint navigator, so you can break at any exceptions. This is simple debug skills you need to master.