As per title, when I try to print the data from viewDidLoad(), nothing is present in the array controller. But when I print the data from one of the tableview methods there is something in there. So is there a method I can use from the viewcontroller class to check when the tableview and it's data has finished loading?
class ViewController: NSViewController {
#IBOutlet var alarmArrayController: NSArrayController!
}
ArrayController's attributes in XCode for ViewController
ArrayController's Cocoa Bindings in XCode for ViewController
I have this block of code to print my array controller.
for object in alarmArrayController.arrangedObjects as! [Alarm] {
print(object)
alarmArrayController.removeObject(object)
}
It works within the following viewtable method
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
but not within viewDidAppear() or viewDidLoad()
You could make your controller an NSFetchedResultsControllerDelegate. "A delegate protocol that describes the methods that will be called by the associated fetched results controller when the fetch results have changed." (Apple docs)
Have your class conform to the protocol:
class YourViewController: UITableViewController, NSFetchedResultsControllerDelegate
Set yourself as delegate when you create the data provider object:
provider.fetchedResultsControllerDelegate = self
Finally, create a class extension as below.
extension YourViewController {
func controllerDidChangeContent(_ controller:
NSFetchedResultsController<NSFetchRequestResult>) {
tableView.reloadData()
}
}
Okay. So I found my answer while I was updating my post.
What I realised was that at some point during the viewcontroller lifecycle, the data is loaded from core data to my array controller with Cocoa bindings. At what stage during this life cycle I had no idea. What also made things worse was I was looking at the docs for lifecycle of UIViewController not NSViewController.
NSViewController which appear to be different
UIViewController
https://developer.apple.com/library/content/referencelibrary/GettingStarted/DevelopiOSAppsSwift/WorkWithViewControllers.html
NSViewController
https://developer.apple.com/documentation/appkit/nsviewcontroller
As we can see there are addditional stages within viewDidAppear() for a NSViewController, these are
updateViewConstraints()
viewWillLayout()
viewDidLayout()
It seems the data isn't loaded until the method viewDidLayout() is called
Related
I have tableviewA in a viewController. I am using XIBs as cells for tableViewA. Inside those XIB, I have another tableViewB. Using delegate, I am trying to refresh tableViewB from viewController once I get data from server.
Code used are as follows. Don't know why tableViewB doesn’t get refreshed once data received from server
1.Define protocol
protocol tblRefresh: NSObject {
func refreshTbl()
}
2.ViewController
class HomeVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
weak var delegateTblRefresh : tblRefresh?
//tableViewA defined here
func getDataFromServer(){
// get data from server
self.delegateTblRefresh?.refreshTbl()
}
}
3.XIB as cell of tableViewA
class Challengers: UITableViewCell, UITableViewDelegate, UITableViewDataSource, tblRefresh {
#IBOutlet weak var tableViewB: UITableView!
let homeVC = HomeVC()
override func awakeFromNib() {
super.awakeFromNib()
homeVC.delegateTblRefresh = self
}
//tableViewB defined here
func refreshTbl() {
tableViewB.reloadData()
}
}
You are creating new instance of HomeVC which is not needed.
You don't need to write your own delegate method to refresh you tableView cell. Just reload the table when you get data from server. For example:
func getDataFromServer()
{
// get data from server
self.tableViewA.reloadData()
}
Or if you want to reload only a specific cell you can do this also.
I want to include a view-based NSTableView in the popover of a Safari App Extension.
Starting with the default project in Xcode, I made the SFSafariExtensionViewController the delegate and datasource for the table view as it is the only content on the popover, and mostly this works.
I can populate the table and implement methods like tableView(_:shouldSelectRow:), yet methods which return a notification object such as tableViewSelectionDidChange(_:) do not get called.
Whilst those methods show a cludgy way of knowing when a row is selected, I am left with no way of knowing when a cell is edited.
As I had to connect the delegate outlet of the NSTableView to the File Owner to allow the delegated methods to work, I also tried connecting the dataSource outlet too, but this rightly did not help.
Here is the essence of my code (which for now includes returning dummy table data to test editing):
class SafariExtensionViewController: SFSafariExtensionViewController {
#IBOutlet weak var tableView: NSTableView!
static let shared: SafariExtensionViewController = {
let shared = SafariExtensionViewController()
shared.preferredContentSize = NSSize(width:445, height:421)
return shared
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func textDidEndEditing(_ notification: Notification) {
NSLog("I will NEVER appear in the console")
}
}
extension SafariExtensionViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return 5
}
}
extension SafariExtensionViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cellView = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? NSTableCellView
cellView?.textField?.stringValue = "Blah"
return cellView
}
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
NSLog("I will appear in the console")
return true
}
func tableViewSelectionDidChange(_ notification: Notification) {
NSLog("I will NEVER appear in the console")
}
func controlTextDidEndEditing(_ obj: Notification) {
NSLog("I will NEVER appear in the console")
}
}
(Obviously I do not need both textDidEndEditing(_:) and controlTextDidEndEditing(_:) but I am just trying everything.)
I am guessing the problem is something to do with the table view not being registered for notifications within a SFSafariExtensionViewController? That object inherits from NSViewController, though, so I would have thought these methods should work automatically.
This is my first time using swift, and it is a long time since I wrote a Mac app. But the actual functionality of the extension works, now I just want to have the ability to customize the settings through the UI.
However there seems to be very little written about Safari app extension programming, Apple's documentation is sparse, and I have not even been able to find any code examples featuring a table view in a popover to learn from.
I am probably missing something very obvious, but I have run out of searches to try on here and the web in general, so any help will be appreciated.
UPDATE:
I think I have an answer, by explicitly linking the NSTextFields in the table to the File's Owner as a delegate, the tableViewSelectionDidChange(_:) and controlTextDidEndEditing(_:) methods are now working. There must have been something else wrong causing the former to not work that I accidentally broke and fix, but it makes some sense for the latter.
That is all I need for the functionality to work, however I am still confused why the textDidEndEditing(_:) is still not working when I am led believe it should.
And in Apple's documentation, textDidEndEditing(_ :) is a method of an NSTextField, which links to a page saying controlTextDidEndEditing(_ :) is deprecated
And I misunderstanding anything?
I think you are not setting up the outlet properly please confirm this. Also check you setting up reusable identifier? identifier. for me all delegate calling without no issue after that.
After Implementing the following (Class Inheritance):
class UIViewControllerA: UIViewControllerB {
}
How to let UIViewControllerA to inherits UIViewControllerB IBOutlets? How can I connect the components at Storyboard to the subclass UIViewControllerA?
If your goal is to let IBOutlets to be inherited, you should do the following:
1- Add the IBOutlets in the Super Class:
Super class (UIViewController) should not be directly connected to any ViewController at the storyboard, it should be generic. When adding the IBOutlets to the super class, they should not be connected to any component, sub classes should do that. Also, you might want to do some work (that's why you should apply this mechanism) for the IBOutlets in the super class.
Super Class should be similar to:
class SuperViewController: UIViewController, UITableViewDataSource {
//MARK:- IBOutlets
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// setting text to the label
label.text = "Hello"
// conforming to table view data source
tableView.dataSource = self
}
// handling the data source for the tableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = "Hello!"
return cell!
}
}
2- Inherit Super Class and Connect the IBOutlets:
Simply, your sub class should be similar to:
class ViewController: SuperViewController {
override func viewDidLoad() {
// calling the super class version
super.viewDidLoad()
}
}
From storyboard, assign the view controller to ViewController (Sub Class), then rebuild the project (cmd + b).
Now, after selecting the desired View Controller and selecting "Connection Inspector", you should see -in the IBOutlets section-:
you can manually connect them to the UI components that exists in your sub class ViewController (drag from the empty circle to the component). they should look like:
And that's it! Your sub class's table view and label should inherit what's included in the super class.
Hope this helped.
ViewController Code
class ViewController: UIViewController {
deinit {
print("ViewController deinitialised")
}
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
self.tableView.dataSource = self
}
func didTapBlue() {
}
}
extension ViewController: UITableViewDataSource, CustomCellDelegate {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCell") as! CustomCell
cell.delegate = self
cell.textLabel!.text = "\(indexPath.row)"
return cell
}
func buttonTapped() {
print("Button tapped")
}
}
CustomCell Code
class CustomCell: UITableViewCell {
deinit {
print("Cell deinitialised")
}
var delegate: CustomCellDelegate! //When protocol Type is A
// weak prefix when protocol Type is B
// weak var delegate: CustomCellDelegate!
#IBAction func buttonClickAction(sender: AnyObject) {
if let del = self.delegate {
del.buttonTapped()
}
}
}
Protocol Type A
protocol CustomCellDelegate{
func buttonTapped()
}
Protocol Type B
protocol CustomCellDelegate: class {
func buttonTapped()
}
I am confused about what is the appropriate way to implement delegate pattern for passing message between Cell And ViewController. I know that if two objects hold each other's reference strongly, there will be a retain cycle and they won't get deallocated in the application lifetime.
In the above code, ViewController doesn't seem to hold reference of Cell. Hence I think it doesn't matter if I use protocol of type A and keep the strong reference of ViewController in cell.
But will my code be any safer if I declare the delegate property as a weakly referenced property? What are the implications of it?
Update:
Turns out that even if the ViewController is not holding direct reference of cell & even if TableView's reference is weak, ViewController is somehow holding strong reference to the cells. When I follow Method A, that is without declaring the delegate to be of weak reference. The deinit methods in Cell and ViewController never gets called. I checked in instruments too. The persistent retain count keeps increasing if I don't declare delegate as weak.
Now the big question is how is ViewController holding strong reference to the cells?
There are a couple things going on there.
Making every ViewController conform to UITableViewDelegate and UITableViewDatasource is needless since you already have UITableViewController and you'll probably need to override those methods anyway. You would be duplicating code at some point in your development lifecycle.
delegates always need to be a weak reference to avoid retain cycles.
Deinitialization Process:
When the view controller is popped out.
Then deinit method is called.
Then only all the other references that view controller is holding is cleared.
Parents deinit triggers, child's deinit triggers then after all the deinit is traversed through then deallocation of parent is done finally at last.
If any of the child is strongly referencing the parent. The deinit of parent never gets called and all the deinitialization process halts. In our case, since cell is retaining view controller strongly. The deinit method of ViewController never gets called. Hence The retain cycle.
Here is great explanation for retain cycle.
I started working on this question app.
I began by tableView of the categories:
For data exchange, I decided to use a protocol:
protocol Category {
func data(object:AnyObject)
}
In the first ViewController has the following code:
class ViewController: UIViewController {
var items:[String] = ["Desktop","Tablet","Phone"]
let CategoriesData:Category? = nil
override func viewDidLoad() {
super.viewDidLoad()
CategoriesData?.data(items)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In the second ViewController (tableView in Container) have the following code:
class CategoriesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, Category {
#IBOutlet var table: UITableView!
var items:[String] = []
func data(object: AnyObject) {
self.items = (object as? [String])!
print(object)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewCell = self.table.dequeueReusableCellWithIdentifier("SegueStage") as! TableViewCell
cell.nameLabel.text = items[indexPath.row]
return cell
}
}
For me, apparently it's all right. But nothing appeared on the simulator.
My question is: If the Container use to present another viewController as passing data by protocols should be done?
EDITED
I answered why the TO:s solution didn't work as intended, but I just realised that I haven't given a viable answer to how to use protocols as delegates for the ViewController -> ViewController communication. I'll leave the half-answer below until someone can possibly answer the full question better.
In the way protocol is used in your code, you define your protocol Category to be a delegate for instances of the type ViewController. When an instance of type ViewController is initialised in---and hence owned locally in the scope of---some other class, the instance can delegate callbacks to the owning class.
The problem is that your CategoriesViewController does not contain any instances of type ViewController. We note that both these classes are, in themselves, subclasses of UIViewController, but none of them contain instances of one another. Hence, your CategoriesViewController does indeed conform to protocol Category, by implemented the protocol method data(...), but there's no ViewController instance in CategoriesViewController that can do callbacks to this function. Hence, your code compile file, but as it is, method data(...) in CategoriesViewController will never be called.
I might be mistaken, but as far as I know, protocol delegates are used to do callbacks between models (for model in MVC design) and controllers (see example below), whereas in your case, you want a delegate directly between two controllers.
As an example of model-delegate-controller design, consider some custom user control, with some key property value (e.g. position in rating control), implemented as a subclass of UIView:
// CustomUserControl.swift
protocol CustomUserControlDelegate {
func didChangeValue(value: Int)
}
class CustomUserControl: UIView {
// Properties
// ...
private var value = 0 {
didSet {
// Possibly do something ...
// Call delegate.
delegate?.didChangeValue(value)
}
}
var delegate: CustomUserControlDelegate?
// ... some methods/actions associated with your user control.
}
Now lets assume an instance of your CustomUserControl is used in a a view controller, say ViewController. Your delegate functions for the custom control can be used in the view controller to observe key changes in the model for CustomUserControl, much like you'd use the inherent delegate functions of the UITextFieldDelegate for UITextField instances (e.g. textFieldDidEndEditing(...)).
For this simple example, use a delegate callback from the didSet of the class property value to tell a view controller that one of it's outlets have had associated model update:
// ViewController.swift
Import UIKit
// ...
class ViewController: UIViewController, CustomUserControlDelegate {
// Properties
// ...
#IBOutlet weak var customUserControl: CustomUserControl!
// Instance of CustomUserControl in this UIViewController
override func viewDidLoad() {
super.viewDidLoad()
// ...
// Custom user control, handle through delegate callbacks.
customUserControl.delegate = self
}
// ...
// CustomUserControlDelegate
func didChangeValue(value: Int) {
// do some stuff with 'value' ...
}
}