How to add tableview datasource and delegate to a separate class/object? - xcode

I'm trying to learn how to make a native MacOS X app with SWIFT. I've no background in using Objective-C and even Xcode for the moment.
I achieved to do a simple app which answer to some button clicks and modify some labels. Now, I want to put some stuff in a table. I've put a NSTableView on my UI and figured out that I will have to implement a class which offer implementation for the NSTableViewDelegate and NSTableViewDataSource protocols.
I think that it would be cleaner, for code organization and readability, to put all the stuff to control my TableView in a separate module of the main AppDelegate. Anyway, any tutorial I've seen so far is just focused on how to implement the protocols and put them all in the AppDelegate class.
I've created a new SWIFT file and put the temporary following code inside it:
import Foundation
import Cocoa
class LogTableController: NSObject, NSTableViewDelegate, NSTableViewDataSource {
#IBOutlet var tableView: NSTableView!
var logTableData:[NSDictionary] = []
func numberOfRowsInTableView (tableView: NSTableView!) -> Int {
return 0
}
func tableView(tableView: NSTableView, objectValueForTableColumn tableColumn: NSTableColumn!, row rowIndex: Int) -> AnyObject! {
return "Hello"
}
}
But I'm unable to find how to proceed to make outlet beetween my TableView and my custom class or an instance of that class. Nothing allow me to do that in the InterfaceBuilder.
I think I'm missing something, any answer on how to organize my code to not put all the stuff in the AppDelegate class would be appreciated.
Thanks !

In Interface Builder you would need to add an object from the object library to the document outline and then set its class to your custom class. Then you can connect the delegate and datasource to that object.

Related

Global variable and optional binding in Swift

I have some quite simple doubt regarding optional binding,global variable & wrapping and unwrapping . Since I am new to SWIFT, its very important to understand the tits & bits of their concepts.
1) In Swift if I declare a global variable, I have 2 options either to make it optional or non optional, so let I am having 2-4 or more optional variables . So is it advisable to optional bind all those variables in
viewDidLoad() method// so that I could use them without any problem of unwrapping and fatal error in my program.
2) Let me make myself more clear by the following example- I have 2 VC in my project VC1 & VC2 . VC2 has a text field in which user enters some value and displays it in a tabelview in VC1.
In Vc1
var namevc1 = NSMutableArray?//holds the input of textfield to be passed from VC2.
As you can see, my VC1 is the first view controller that loads when my project runs and I am using an optional variable to populate my tabke vuew that is
'arr'
So when the app runs for the first time its empty . So it might cause a fatal error while using its value in the code. So what is its solution whether to unbind it in the
viewDidLoad()
method or in all total declare an empty NSMutable array type in place of optional type .
Thanks in advance.
I'll start by repeating the my comment from above.
Possibly you've misunderstanding the concept of global variables in Swift.
If you have a global variable, you won't have to "pass" it between any views/methods/classes etc, because the variable is defined at global scope (accessible everywhere).
Generally global variables is not a good idea, and something that you want to avoid.
Regarding the matter of global variables and swift, you really should include singletons into the discussion. See e.g. the following existing SO thread(s):
Any reason not use use a singleton "variable" in Swift?
(How to create a global variable?)
(Declaring Global Variables in Swift)
Communication between TableViewController and ViewController by means of segues (prepare for & unwind segues)
(This answer ended up being very and probably a bit too thorough, as I didn't know in detail what your current tableview/viewcontroller program state looks like. Sorry for the lengthy answer and any inconvenience it might bring to readers of it).
Now, lets leave global variables and discuss one (among other) viable options for the communication between the two controllers in your example. From your question, I'll summarize your example as follows
VC1: storyboard entry point, a UITableViewController consisting of UITableViewCells, where, in these cells, you display some text, say, via instances of UILabel.
VC2: a UIViewController, accessible from the cells of VC1, containing an UITextField instance. When user enters text into this text field, your want the text to be displayed in the associated cell in VC2 (associated in the sense that it was the cell in VC1 that was used to access VC2).
We'll associate VC1 and VC2 with (cocoa touch) classes TableViewController (TableViewController.swift) and ViewController (ViewController.swift), respectively. The cells in the table view controller will be associated with (cocoa touch) class TableViewCell (TableViewCell.swift). Details for these classes follow below.
For this simple example, note that we will not embed VC1 into a navigation controller (which is otherwise appropriate for table view -> view navigation).
We'll start in the storyboard, adding objects (drag-and-drop from object library) for a Table View Controller and a View Controller. The table view container will also, automatically, contain, in its Table View, a TableViewCell. Continuing in the storyboard:
Add a UILabel object to the TableViewCell container in the Table View Controller (align it as you wish)
In the View Controller, add a Text Field object and a Button object (align them as you wish).
Set the entry point to the Table View Controller.
Thereafter Ctrl-drag a 'Show' segue from the TableViewCell to the View Controller.
Select the Show segue and, from the Attributes inspector, enter an identifier for it, say, ShowDetail.
Finally, with the TableViewCell selected, (as above; from the attribute inspector), enter an identifier for the cell. Here, we'll use simply use identifier TableViewCell.
We now leave the storyboard for now and implement three classes, associated with the Table View Controller, the View Controller and the formers' TableViewCell.
We start with the Table View Controller, and implement our UITableViewController sub-class. Note that here, instead of using an NSMutableArray to hold the texts of the UITextLabel in each cell, we'll simply use a String array.
// TableViewController.swift
Import UIKit
class TableViewController: UITableViewController {
// Properties
var userTextLabels = [String]()
var numberOfCells: Int?
override func viewDidLoad() {
super.viewDidLoad()
numberOfCells = loadSampleTextLabels() // Load sample labels.
}
func loadSampleTextLabels() -> Int {
userTextLabels += ["Label #1", "Label #2", "Label #3"]
return userTextLabels.count
}
// func numberOfSectionsInTableView(tableView: UITableView) ...
// func tableView(tableView: UITableView, numberOfRowsInSection section: Int) ...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = ("TableViewCell")
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TableViewCell
// Text for current cell
let cellText = userTextLabels[indexPath.row]
cell.userSuppliedTextLabel.text = cellText
return cell
}
// ... communication?
}
Where the two commented out methods are standard methods used in any UITableViewController, for number of sections (e.g. return 1) and cells (e.g. return (numberOfCells ?? 0)) in the table, respectively. I'll leave fixing these to you.
Now, we associate the TableViewCell object(s) in the table view with instances of a subclass to UITableViewCell. Here, we'll use a very simple class for our cells; each cell just containing a single UILabel instance (created via storyboard Ctrl-drag as an #IBOutlet from the UILabel in the table view cells).
// TableViewCell.swift
import UIKit
class TableViewCell: UITableViewCell {
// Properties
#IBOutlet weak var userSuppliedTextLabel: UILabel!
// Ctrl-drag from UILabel (in TableViewCell) in storyboard
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Finally, for the view controller that is accessed from the table view cells: use a single #IBOutlet to the UITextField used for user text input, and handle events in this text field using the pre-existing UITextFieldDelegate. E.g.:
// ViewController.swift
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
// Properties
#IBOutlet weak var userSuppliedText: UITextField!
// Ctrl-drag from storyboard...
var cellText: String?
override func viewDidLoad() {
super.viewDidLoad()
userSuppliedText.text = cellText ?? "..."
// Handle the user input in the text field through delegate callbacks
userSuppliedText.delegate = self
}
// UITextFieldDelegate
func textFieldShouldReturn(textField: UITextField) -> Bool {
// User finished typing (hit return): hide the keyboard.
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
cellText = textField.text
}
}
We've also declared a string property (cellText) here, that will as act as container for communication between VC1 and VC2.
We return to the storyboard and---from the Identity inspector---associate the three storyboard objects (Table View Controller, View Controller, TableViewCell) with their associated classes that we've just written above.
We're now almost at our goal; it only remains to specify how to communicate between the two controllers.
We'll begin with communication from VC1 to VC2. In your comment above, you were on the right track (for this specific solution, anyway) by looking at the prepareForSegue(...) method. In the class for the Table View Controller, we add the following method:
// ... add to TableViewController.swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "ShowDetail" {
let viewController = segue.destinationViewController as! ViewController
if let selectedCell = sender as? TableViewCell {
let indexPath = tableView.indexPathForCell(selectedCell)!
let currentTextInCell = userTextLabels[indexPath.row]
viewController.cellText = currentTextInCell // <-- note this
}
}
}
Hence, for VC1->VC2 communication, we can (in this example) bring whatever existing text that is currently occupying the UILabel in the sender cell (as is specified by the String array userTextLabels). Look at the viewDidLoad(...) method in the ViewController.swift to see how this value is passed from VC1 and set as default text in the UITextField in VC2.
Now, for communication VC2->VC1, which was the specific communication direction you were asking about, add another method (programmatically), again to TableViewController.swift:
// ... add to TableViewController.swift
#IBAction func unwindToTableView(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? ViewController,
text = sourceViewController.cellText {
// ^ note 2nd clause of if let statement above
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update cell text
userTextLabels[selectedIndexPath.row] = text
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
}
}
}
Here, we define an unwind action that, when triggered, retrieves the cellText property of the view controller that was the source of the segue, i.e., in our case, the instance of ViewController. But how do we trigger this action?
Return to the storyboard and the View Controller. Note the three little icons in the top of the View Controller object, more specifically, the right-most of these, named Exit. Ctrl-drag an action from your Button to the Exit icon, and select the unwindToTableView Action Segue. When you click your button the view controller, the view unwind (exit) and land at the unwindToTableView method in the TableViewController.
The resulting app should look something like this:
This was way longer than I had expected, but once you get started writing... Anyway, the method above uses, naturally, no global variables, but make use of references to future (prepareForSegue) or historic (unwindToTableView) views to get (generally from current or historic view) or set (generally in current of future view) values by using these references (to future/historic view).
Apple has their own very thorough tutorial on an example app in the tableviewcontroller/viewcontroller context that I would recommend going over. I found it very valuable myself when I started coding Swift.
Start Developing iOS Apps (Swift)

How do I subscribe to TextField's TextChanged event in Xcode

I've recently started working on some test projects to get the feel for OS X development with Xcode. I come from Windows, so I might not be making much sense here.
How would I subscribe to certain "events" in Swift? I have just learned how to connect actions to UI objects. For example, I can now click a button, and change the text of a label programatically. However, and this may just be a case of lack of knowledge on my part - I am not able to find a way to subscribe to a TextField's "Text Changed" event.
Let's say that I have a TextField, and when I change the text at runtime (i.e. type something), I want to do something in the textChanged event for that particular TextField.
Is there even such a thing as a TextChanged event in OS X development?
Update
I am now using the following code:
import Cocoa
class ViewController: NSViewController {
class textField:NSTextField, NSTextFieldDelegate
{
override func awakeFromNib() {
delegate = self;
}
override func controlTextDidChange(obj: NSNotification)
{
println("Text changed.")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
And I have added a ClassName to the TextField control in the Identity Inspector, but it isn't responding to the text changing. The message given is:
Failed to connect (textField) outlet from
(Xcode_Action_Basics.ViewController) to (NSTextField): missing setter
or instance variable
I just googled that error and came across this page: Failed to connect (storyboard) outlet from (NSApplication) to (NSNibExternalObjectPlaceholder) error in Cocoa and storyboard which states that this is a known issue in Xcode and that it does not mean there is a problem with your code - but I'm not so sure about that, because the code isn't working. Not sure if I've missed out on something.
Create a class that implements the protocol NSTextFieldDelegate like
class MyTextField:NSTextField, NSTextFieldDelegate {
override func awakeFromNib() {
delegate = self // tell that we care for ourselfs
}
override func controlTextDidChange(obj: NSNotification) {
// .... handle change, there are a lot of other similar methods. See help
}
}
In IB assign this class here:

Call function in view controller from UITableViewCell

I have a problem which drives me nuts, how is it possible to invoke a function in a view controller from within a UITableViewCell. - I am doing that in Swift.
So I have a View Controller - called mainVC
in this mainVC I have declared a tableView which holds a UITableViewCell (Custom/ Dynamic) in that tableView cell I want call a function which is declared in the mainVC. If I do like self.superview?.myFunc doesn't work.
I hope someone can help me...
It is not as easy as that unfortunately.
What you want to look into is NSNotificationCenter or even better: - Delegates.
If you want a better understanding of Delegates I'd recommend this tutorial: http://www.raywenderlich.com/75289/swift-tutorial-part-3-tuples-protocols-delegates-table-views
If you don't want to read all that, I'll try my best to explain it here.
Let's say you have two view controllers: mainVC and otherVC.
Above the class definition in otherVC, add the following code:
protocol OtherVCDelegate {
func cellClicked()
}
Inside the class in otherVC, add the following property:
var delegate: OtherVCDelegate?
In the didSelectRow (or where you execute the code), add the following code:
self.delegate?.cellClicked()
Now, back in mainVC: Make mainVC conform to this delegate by adding it to the class like this:
class MainViewController: UIViewController, DetailDelegate {
}
In MainViewController add the delegate function and insite that one put your function you want to execute:
func cellClicked() {
println("Cell was clicked")
myFunc()
}
The last thing you have to do is make MainViewController the delegate of OtherViewController. This has to be done where you access the otherVC from the mainVC. Let's say it is done in prepareForSegue:
if segue.identifier == "showDetail" {
(segue.destinationViewController as DetailViewController).delegate = self
}
It is a little bit complicated at first, but once you get a grasp of it it's a walk in the park.

OS X addsubview from xib in swift

I'm trying to add a new sub view form a nib using swift for OS X.
So far i've:
created a new "Cocoa Application"
added a new "Cocoa Class" called "TestSubView" as a subclass of NSViewController with a XIB file
I want to add this subview to my main view when the application loads.
in my ViewController ( the ViewController for the main window ) i have.
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let newSubView = TestSubView();
self.view.addSubview(newSubView.view);
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
But i'm getting the following error
Failed to set (contentViewController) user defined inspected property on (NSWindow):
-[NSNib initWithNibNamed:bundle:] could not load the nibName: temp.TestSubView in bundle (null).
I realise i will need to size and position this subview but I can't seem to get to that point.
I've spent the better part of a day trying to figure this one out so any help would be greatly appreciated.
I finally got this thing to work. My new code looks like
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let subview = TestSubView(nibName: "TestSubView", bundle: nil)!
self.view.addSubview(subview.view)
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
Found with the help of the docs & this answer
It was suggested that if the nib name and the class name are the same you shouldn't need to declare nibname: (as i'd tried to do originally) but the docs didn't mention this - explains why it didn't work!
For prosperity, this worked for me with Xcode 6.1.1 on OS X Yosemite (10.10.1)
A nib is really nothing but an XML file with view information in it. You have to get it from the application bundle and get one of the views contained in it explicitly. You are perhaps confounding views and view controllers (your attempt to extract view from newSubView suggests that).
Try this:
let subview = NSBundle.mainBundle().loadNibNamed("TestSubView",
owner:self, options:nil)![0]! // maybe no final unwrapping "!" in Swift 3
self.view.addSubview(subview)
Make sure the xib is really called the name you are using and contains at a least one view (otherwise the two unwrapping ! above will crash your app).

Xcode6/Swift - How to implement an iAdBannerView in multiple view controllers?

Before I get started I am aware this question has been asked many times before, however all of them refer to xcode5/objective-C, not swift. I am only new to app development so I haven't been able to understand the objective-c and use it in swift.
I have got an adBannerView working on my first view controller, however how do I then take this banner and use it across my other 2 view controllers? Do I use the prepareForSegue function (and if so, how)?
My code for the adBannerView I currently have (from here)
//...
import iAd
class ViewController: UIViewController, ADBannerViewDelegate {
//link adBanner
#IBOutlet var adBannerView: ADBannerView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.canDisplayBannerAds = true
self.adBannerView.delegate = self
self.adBannerView.hidden = true
}
func bannerViewWillLoadAd(banner: ADBannerView!) {
NSLog("bannerViewWillLoadAd")
}
func bannerViewDidLoadAd(banner: ADBannerView!) {
NSLog("bannerViewDidLoadAd")
self.adBannerView.hidden = false
}
func bannerViewActionDidFinish(banner: ADBannerView!) {
NSLog("bannerViewDidLoadAd")
//optional resume paused game code
}
func bannerViewActionShouldBegin(banner: ADBannerView!, willLeaveApplication willLeave: Bool) -> Bool {
NSLog("bannerViewActionShouldBegin")
//optional pause game code
return true
}
func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) {
NSLog("bannerView")
}
//...
Thanks :)
When I have faced this problem my solution was to create a ParentViewController, and the others view controllers inheritate from him. In the parent view controller I create an outlet for a view which will contain iAdView, and in the .xib file of each view controller I link the iAdView container view to the created outlet. After that I create a singleton which has all the iAdView functionality and a property which is the iAdView. In the viewDidAppear of the parent I ask the singleton for the iAdView and add it as a subview of the iAd view container. Doing this that way in the view controllers you are not going to see any code of the iAdView, because all will be in the ParentViewController and in the singleton (lets call it iAdManager). Hope it helps.

Resources