Kodeco Forums

Storyboards Tutorial in iOS 9: Part 1

Learn how to design your user interface visually with less code in this Storyboards tutorial, fully up-to-date for iOS 9.


This is a companion discussion topic for the original entry at http://www.raywenderlich.com/113388/storyboards-tutorial-in-ios-9-part-1

Hello,

I would like to know how you would go about creating the players list without using the SampleData which automatically loads the names entered in that Swift file; in other words, displaying a blank players list at runtime but keeping the structure Player, Game and Rating. Thank you in advance.

Laroms

Hi Laroms - youā€™ll need to save your data in some way.

This could be:

  1. A plist
  2. Core Data
  3. SQL
  4. Realm
  5. NSUserDefaults

This tutorial will get you started with Core Data - it shows storing data in a database and loading to a table view:

http://www.raywenderlich.com/115695/getting-started-with-core-data-tutorial

This is a similar tutorial for Realm:

http://www.raywenderlich.com/112544/realm-tutorial-getting-started

Hi Caroline,

Thank you ever so much! I was able to find a fix in my case with - var lessons:[Lesson] = [ ] - i.e. leaving the square brackets empty - is that any good?

I have not looked at the links you sent me yet, but will they answer the following:

I have the 4th Edition of the iOS Apprentice book with me, and I have run into an obstacle in the Saving and loading the checklist items section and more particularly in The documents directory section - following your Storyboards tutorial, I never called the method - required init?(coder aDecoder: NSCoder) (page 174), but can I still call it with the two print statements the author mentions (pp237-238)? Basically, I want my app to store the lessons added onto the list (lesson #1, lesson #2 etc) when the user either goes back to a previous view, moves a row, deletes one, updates the app, terminates it etc.

A huge thank-you,

Best, Laroms

Hi Laroms - var lessons:[Lesson] = [] will instantiate an empty array of lessons.

I havenā€™t read iOS Apprentice myself - I wish it had been around while I was learning, though. It looks like heā€™s saving the data in a totally different way from what I suggested!

Just as good, I hasten to add - if your data is as simple as a checklist, then NSKeyedArchiver(_:slight_smile: will work great instead of having to learn how to use databases.

Iā€™m not sure I understand your obstacle. If youā€™ve set up the documents directory correctly into a variable, you can use that variable as long as itā€™s in scope.

And if Iā€™m understanding correctly, you put all your saving out to disk code in one method and then call that method whenever you want to write out to disk.

Hi Caroline,

Thanks for the swift reply (pun not made on purpose, here :slight_smile: !).

Well, my app is: one tableView Controller with 6 cells (one for each school day), one cell pushes to its respective tableView Controller displaying rows (one row per lesson).

I am able to create and display the list, but whenever I go back to my first tableView Controller (the one with the days of the week) and back again to the Monday lessons list, the list is emptyā€¦ I do not know how to make my app retain the lessons listā€¦ So I am well stuck here, and thought to have found the solution in the book.

Your help/advice will be greatly appreciated.

Best, Laroms

Instead of doing var lessons:[Lesson] = [] which makes an empty array, perhaps you should load your data into the array instead?

Or you could load your initial data in viewDidLoad() in your tableViewController and nowhere else. That would make it static throughout the app?

Difficult for me to comment as I canā€™t visualise your code!

Thanks, Caroline. Hereā€™s where I got to so far:

  1. MondayWeekALessons (list of lessons on Monday)

  2. Monday cell

  3. Lesson.swift file

  4. LessonSampleData.swift file. In 3) I do not wish to use the data I have placed here but I wish to keep the structure which is displayed with 4 Labels.

  5. import UIKit

class MWALessonsViewController: UITableViewController {

var lessons:[Lesson] = []

override func viewDidLoad() {
    super.viewDidLoad()

    // Uncomment the following line to preserve selection between presentations
    // self.clearsSelectionOnViewWillAppear = false

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem()
}

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


@IBAction func editingRow(sender: UIBarButtonItem) {
    self.editing = !self.editing
}

// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    // Return false if you do not want the specified item to be editable.
    return true
}



// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == .Delete {
        
        self.lessons.removeAtIndex(indexPath.row)
        
        // Delete the row from the data source
        tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    } else if editingStyle == .Insert {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }
}

// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
    
    let itemToMove = lessons[fromIndexPath.row]
    
    lessons.removeAtIndex(fromIndexPath.row)
    
    lessons.insert(itemToMove, atIndex: toIndexPath.row)
    
}


// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return lessons.count
}


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCellWithIdentifier("MWALessonsCell", forIndexPath: indexPath) as! MWALessonsCell
    let lesson = lessons[indexPath.row] as Lesson
    cell.lesson = lesson
    return cell
}

override func viewDidAppear(animated: Bool) {
    
    let nav = self.navigationController?.navigationBar
    
    nav?.barStyle = UIBarStyle.Default
    nav?.tintColor = UIColor.cyanColor()
    
    let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 45, height: 45))
    imageView.contentMode = .ScaleAspectFit
    
    let image = UIImage(named: "MCButton")
    imageView.image = image
    
    navigationItem.titleView = imageView
    
}

@IBAction func backToLessonsViewController(segue:UIStoryboardSegue) {
}

@IBAction func saveLessonDetail(segue:UIStoryboardSegue) {
    if let mwaLessonDetailsViewController = segue.sourceViewController as? MWALessonDetailsViewController {
        
        if let lesson = mwaLessonDetailsViewController.lesson {
            lessons.append(lesson)
            
            let indexPath = NSIndexPath(forRow: lessons.count-1, inSection: 0)
            tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
            
        }
    }
}

import UIKit

class MWALessonsCell: UITableViewCell {

@IBOutlet weak var timeLabel: UILabel!
@IBOutlet weak var subjectLabel: UILabel!
@IBOutlet weak var placeLabel: UILabel!
@IBOutlet weak var homeworkDayLabel: UILabel!


var lesson: Lesson! {
    didSet {
        timeLabel.text = lesson.time
        subjectLabel.text = lesson.subject
        placeLabel.text = lesson.place
        homeworkDayLabel.text = lesson.homeworkDay
        
    }
}

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code
}

override func setSelected(selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state
}

}

import UIKit

struct Lesson {

var time: String?
var subject: String?
var place: String?
var homeworkDay: String?

init(time: String?, subject: String?, place: String?, homeworkDay: String?){
    self.time = time
    self.subject = subject
    self.place = place
    self.homeworkDay = homeworkDay

}
}

import Foundation

let lessonsData = [
Lesson(time: ā€œ8.20amā€, subject: ā€œHomeroomā€, place: ā€œRoom S2ā€, homeworkDay: ā€œā€) ]

Hi Laroms

That doesnā€™t help a great deal. It doesnā€™t show how you are initially loading lessons, how you instantiate a new lesson or how you move data from the screen into the new lesson.

I canā€™t see anything wrong with the code as far as it goes.

Youā€™re only adding not editing lessons? I donā€™t see prepareForSegue anywhere in your code to pass data to the detail.

Hi Caroline,

Thanks for having a look.

Sorryā€¦ I think I have found a way to store those lessons for a given day following the iOS Apprentice (this really is me!) method, but I have not tried it yet. I will definitely tell you if I am successfulā€¦or not!

I have another question, though: how do you update an existing player in your tutorial?

Thank you ever so much.

Best, Laroms

Thatā€™s not included in the tutorial, but this is the way I would attack it.

Iā€™d create an Edit segue in the storyboard from the table row to the detail scene.

Iā€™d have a prepareForSegue method that checks if this is an Edit segue, and if it is, pass the data from the cell (which is the sender) to the detail view controller.

I think Iā€™d have a property in either the master or the detail holding whether itā€™s an Edit or an Add, so that the save can either change or append.

Hello
I want to develop an ios application for the existing web page. For that i want to break down the webpage into parts and display it in application. How can i do this

Thank you.

Thatā€™s called scraping. This is an older tutorial: https://www.raywenderlich.com/14172/how-to-parse-html-on-ios but perhaps it might get you started searching for terms that might help you.

I think I must be dense, but I am having a hard time understanding how to store more than one thing at a time in an NSCoder.

My app keeps an internal running history of dice rolls, and what Iā€™m trying to do is enable the saving or loading of saved histories.

I can get one, if I try to store the next, I get a message about the new values clobbering the older ones.

Hereā€™s what Iā€™ve got so far:

func encodeWithCoder(aCoder: NSCoder) {
	
	for stackItem in 0..<self.abilityScoreHistoryStack.count {
		
		for roll in 0..<self.abilityScoreHistoryStack[stackItem].rolls.count {
			
			aCoder.encodeInteger(self.abilityScoreHistoryStack[stackItem].rolls[roll], forKey: PropertyKey.rollsKey)
			
		}
		aCoder.encodeInteger(self.abilityScoreHistoryStack[stackItem].numberOfDice, forKey: PropertyKey.numDiceKey)
		aCoder.encodeInteger(self.abilityScoreHistoryStack[stackItem].sizeOfDice, forKey: PropertyKey.dieSizeKey)
		aCoder.encodeInteger(self.abilityScoreHistoryStack[stackItem].droppedDice, forKey: PropertyKey.droppedKey)
		aCoder.encodeInteger(self.abilityScoreHistoryStack[stackItem].modifier, forKey: PropertyKey.modKey)
		aCoder.encodeObject(self.abilityScoreHistoryStack[stackItem].itemTitle, forKey: PropertyKey.itemtitleKey)
		aCoder.encodeBool(self.abilityScoreHistoryStack[stackItem].rollSortStatus, forKey: PropertyKey.rollSortStatusKey)
		
	}
	
	
	
	
}

and to trigger it, in the view controller (and triggered by a tap on a button):

@IBAction func saveHistoryButtonAction(sender: UIButton) {
	
	for _ in 0..<self.charGen.abilityScoreHistoryStack.count {
	
		saveHistory()
		
	}
	
}
// MARK: NSCoding
func saveHistory () {
	
	let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(self.charGen, toFile: CharacterGenerator.ArchiveURL.path!)
	
	if (!isSuccessfulSave) { print("Save failed. Try something different") }
	// else {  }
	
}

I think I understand why Iā€™m getting message that the new values clobber the old, but I am having a devil of a time thinking of another way to handle the fact that thereā€™s an array of these rolls (the history stack is an array of structs defined in the class).

Hi qrs - is this post in reference to a NSCoder tutorial? This thread is for a Storyboards tutorial.

Yes, sorry. This was the only forum that showed up when I searched for NSCoder, is there a better place for it?

I was able to find the right tutorial! Sorry for misplacing it

Hi, Caroline,

one simple little thingā€¦

When I look at the bottom of the tab bar controller in the storyboard editor, I donā€™t see two items, like in your picture, even though they appear when i run the app. instead, itā€™s just a dark grey bar all the way across the bottom of the controllerā€¦ what did I do wrong?

hereā€™s what it looks like:

Does it do it every time?

The only thing I could find about it was in Xcode 5, but the bug may still be there:

Hi Caroline,
I am having a problem with my ratings app.
I followed the tutorial up to the point before creating custom cells.
And when I try to run I get this error:

2016-07-10 11:40:35.868 Ratings[18408:3402377] *** Assertion failure in -[UITableView _configureCellForDisplay:forIndexPath:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.60.7/UITableView.m:7971
2016-07-10 11:40:35.871 Ratings[18408:3402377] *** Terminating app due to uncaught exception ā€˜NSInternalInconsistencyExceptionā€™, reason: ā€˜UITableView (<UITableView: 0x7fe4c4033400; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7fe4c2dc51e0>; layer = <CALayer: 0x7fe4c2da2d10>; contentOffset: {0, -64}; contentSize: {375, 132}>) failed to obtain a cell from its dataSource (<Ratings.PlayersViewControllerTableViewController: 0x7fe4c2daaa20>)ā€™
*** First throw call stack:
(
0 CoreFoundation 0x000000010cd67d85 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x000000010eb0bdeb objc_exception_throw + 48
2 CoreFoundation 0x000000010cd67bea +[NSException raise:format:arguments:] + 106
3 Foundation 0x000000010d1b8d5a -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 198
4 UIKit 0x000000010d6e14b1 -[UITableView _configureCellForDisplay:forIndexPath:] + 225
5 UIKit 0x000000010d6ed51e -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 808
6 UIKit 0x000000010d6ed62c -[UITableView _createPreparedCellForGlobalRow:willDisplay:] + 74
7 UIKit 0x000000010d6c1d4f -[UITableView _updateVisibleCellsNow:isRecursive:] + 2996
8 UIKit 0x000000010d6f6686 -[UITableView _performWithCachedTraitCollection:] + 92
9 UIKit 0x000000010d6dd344 -[UITableView layoutSubviews] + 224
10 UIKit 0x000000010d64a980 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 703
11 QuartzCore 0x0000000112015c00 -[CALayer layoutSublayers] + 146
12 QuartzCore 0x000000011200a08e _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 366
13 QuartzCore 0x0000000112009f0c _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
14 QuartzCore 0x0000000111ffe3c9 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 277
15 QuartzCore 0x000000011202c086 _ZN2CA11Transaction6commitEv + 486
16 QuartzCore 0x000000011202c7f8 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 92
17 CoreFoundation 0x000000010cc8cc37 CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION + 23
18 CoreFoundation 0x000000010cc8cba7 __CFRunLoopDoObservers + 391
19 CoreFoundation 0x000000010cc8211c CFRunLoopRunSpecific + 524
20 UIKit 0x000000010d58af21 -[UIApplication _run] + 402
21 UIKit 0x000000010d58ff09 UIApplicationMain + 171
22 Ratings 0x000000010cb80302 main + 114
23 libdyld.dylib 0x000000010f5cf92d start + 1
24 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException