How to call reloaddata() from another class? - swift

I have two classes NSViewController, which contains a NSTableView, and a NSPageController, which can be used to add data to the table view. How can I call reloadData() from NSPageController to update the table view of NSViewController?
Update:
I already tried to solve this with a delegate and I already have done this in Objective-C, but with swift I feel completely lost.
My PageController needs to notify the ViewController, that the tableView has to be updated. So I added this code to my PageController
protocol PageControllerDelegate {
func updateTableView()
}
and
weak var delegate: PageControllerDelegate?
To my ViewController which hold the table view I added the following lines:
class ViewController: NSViewController, PageControllerDelegate {
...
and
func updateTableView() {
self.tableView.reloadData()
}
Xcode gives me the following errors:
'weak' may only be applied to class and class-bound protocol types, not 'PageControllerDelegate'
Property 'delegate' with type 'PageControllerDelegate?' cannot override a property with type 'NSPageControllerDelegate?' (aka 'Optional')

I can give you only a generic answer.
First of all, you need a contact for your view controllers. Like NSTableViewController or something that you defined (a Protocol).
Example, when you using a table view controller:
(viewController as? NSTableViewController)?.tableView.reloadData()
And an example, when you using a view controller with an contract:
The contact:
protocol HasTableView {
var tableView: NSTableView? { get }
}
The view controller conforming to the contract:
class ExampleViewController: NSViewController, HasTableView {
var tableView: NSTableView? { return myTableView }
...
}
And your reload data call:
(viewController as? HasTableView)?.tableView?.reloadData()
Hope it’s helps.

A simple way to do, depending on your control hierarchy - if your NSViewControllers hold your controller, then give our controller class a weak var delegate: NSViewControllerDelegate, and define a `protocol
NSViewControllerDelegate: AnyObject {
func controllerShouldUpdateTableView(_ controller: NSController)
}
have your NSViewController conform to this protocol, and then make sure set the delegate on the controller. Just call this function when needed to reload your table.

Related

Weak and delegate fail-warnings when trying to update tableview through delegate method

I been struggling to update my tableview through another class I made.
I then found this stackoverflow solution:
How to access and refresh a UITableView from another class in Swift
But when I follow it step by step and implement all the codes, I get the following errors:
My line:
weak var delegate: UpdateDelegate?
Gets the warning
'weak' may only be applied to class and class-bound protocol types, not 'UpdateDelegate'
And my line:
self.delegate.didUpdate(self)
Gets warning:
Instance member 'delegate' cannot be used on type 'APIgetter'
Could this be because the code is old and I'm using swift 4? else I cannot see why this should be failing. I hope you can help me :)
Update:
My Protocol:
protocol UpdateDelegate: AnyObject {
func didUpdate(sender: APIgetter)
}
Snippet from my ViewController containing the tableview:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UpdateDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
APIgetter.addDataFromSQL()
let updates = APIgetter()
updates.delegate = self
}
//update func
func didUpdate(sender: APIgetter) {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
My APIgetter class in APIgetter.swift:
class APIgetter {
weak var delegate: UpdateDelegate?
class func addDataFromSQL (){
//Code to fetch data from API
//Code that comes after DispatchQueue.global & DispatchQueue.main and my result being executed
//result
self.delegate.didUpdate(self)
just update your protocol definition.
protocol UpdateDelegate: class {
// protocol body
}
or
protocol UpdateDelegate: AnyObject {
// protocol body
}
This is needed (as of Swift 4 I think) because classes are reference types and you can only use a weak reference on reference types. Not value types like structs.
UPDATE: You cannot access a property/instance member from a static function the way that you currently are. Remove the class keyword from the function and it should work.
If you want/need to use a single instance of this class throughout your application you can use a static property to make it a Singleton
class APIgetter {
static let shared: APIgetter = APIgetter()
}
Then you would be able to access it like this:
APIgetter.shared.addDataFromSQL()
You could also update the delegate in the same way before calling your function.
APIgetter.shared.delegate = self
I think in this case though I would use a Singleton without the delegate. Just use a completion handler in your function. Setting and changing the delegate on a shared instance could have some side effects if not managed carefully.

Change #IBOutlet from a subview

I'm trying to enable or disable an #IBOutlet UIButton Item of a toolbar from a UIView.
The button should get disabled when the array that I'm using in EraseView.Swift is empty.
I tried creating an instance of the view controller but it gives me the error (found nil while unwrapping):
in EraseView:
class EraseView: UIView {
...
let editViewController = EditImageViewController()
//array has item
editViewController.undoEraseButton.enabled = true //here I get the error
...
}
I tried to put a global Bool that changed the value using it in EditImageViewController but it doesn't work:
var enableUndoButton = false
class EditImageViewController: UIViewController {
#IBOutlet weak var undoEraseButton: UIBarButtonItem!
viewDidLoad() {
undoEraseButton.enabled = enableUndoButton
}
}
class EraseView: UIView {
...
//array has item
enableUndoButton = true //here I get the error
...
}
I know it's simple but I can't let it work. Here's the situation:
The root of the problem is the line that says:
let editViewController = EditImageViewController()
The EditImageViewController() says "ignore what the storyboard has already instantiated for me, but rather instantiate another view controller with no outlets hooked up and use that." Clearly, that's not what you want.
You need to provide some way for the EraseView to inform the existing view controller whether there was some change to its "is empty" state. And, ideally, you want to do this in a way that keeps these two classes loosely coupled. The EraseView should only be informing the view controller of the change of the "is empty" state, and the view controller should initiate the updating of the other subviews (i.e. the button). A view really shouldn't be updating another view's outlets.
There are two ways you might do that:
Closure:
You can give the EraseView a optional closure that it will call when it toggles from "empty" and "not empty":
var emptyStateChanged: ((Bool) -> ())?
Then it can call this when the state changes. E.g., when you delete the last item in the view, the EraseView can call that closure:
emptyStateChanged?(true)
Finally, for that to actually do anything, the view controller should supply the actual closure to enable and disable the button upon the state change:
override func viewDidLoad() {
super.viewDidLoad()
eraseView.emptyStateChanged = { [unowned self] isEmpty in
self.undoEraseButton.enabled = !isEmpty
}
}
Note, I used unowned to avoid strong reference cycle.
Delegate-protocol pattern:
So you might define a protocol to do that:
protocol EraseViewDelegate : class {
func eraseViewIsEmpty(empty: Bool)
}
Then give the EraseView a delegate property:
weak var delegate: EraseViewDelegate?
Note, that's weak to avoid strong reference cycles. (And that's also why I defined the protocol to be a class protocol, so that I could make it weak here.)
The EraseView would then call this delegate when the the view's "is empty" status changes. For example, when it becomes empty, it would inform its delegate accordingly:
delegate?.eraseViewIsEmpty(true)
Then, again, for this all to work, the view controller should (a) declare that is conforms to the protocol; (b) specify itself as the delegate of the EraseView; and (c) implement the eraseViewIsEmpty method, e.g.:
class EditImageViewController: UIViewController, EraseViewDelegate {
#IBOutlet weak var undoEraseButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
eraseView.delegate = self
}
func eraseViewIsEmpty(empty: Bool) {
undoEraseButton.enabled = !empty
}
}
Both of these patterns keep the two classes loosely coupled, but allow the EraseView to inform its view controller of some event. It also eliminates the need for any global.
There are other approaches that could solve this problem, too, (e.g. notifications, KVN, etc.) but hopefully this illustrates the basic idea. Views should inform their view controller of any key events, and the view controller should take care of the updating of the other views.

can not delegate the detail view controller to the master view controller

Hello I tried using delegate to connect my Master and Detail view controllers.
In the master view controller I got this above the class:
protocol CategorySelectionDelegate: class {
func categorySelected(id: Int)
func progress(percentage: Float)
}
and within the class I declare it as:
weak var delegate: CategorySelectionDelegate?
In the detail view controller I do
extension DetailViewController: CategorySelectionDelegate {
func categorySelected(id: Int) {
print("set row \(id)")
selectedCategoryID = id
}
func progress(percentage: Float) {
dispatch_async(dispatch_get_main_queue(), {
self.progressView.setProgress(percentage, animated: true)
})
}
}
So far so good. Everything works. But when I try to connect them both by doing
self.delegate = DetailViewController
Inside the viewDidLoad in my MasterViewController it says "cannot assign value of type 'DetailViewController.Type' to 'CategorySelectionDelegate?'"
What am I Doing wrong here?
First of all you are assigning delegate in wrong class if you have created delegate in master view controller class then you want to respond to delegate in detail view controller then you need to assign like this in Detail view controller...
Let masterVC : MasterlViewController = MasterViewController()
masterVC.delegate = self
Now in above line you will get same error as you told above so you must include delegate name in class as we do for uitableview datasource ...
Class DetailViewController :UIViewController ,CategorySelectionDelegate

How to subclass custom UIViewController in Swift?

I'd like to create a reusable view controller UsersViewControllerBase.
UsersViewControllerBase extends UIViewController, and implements two delegates (UITableViewDelegate, UITableViewDataSource), and has two views (UITableView, UISegmentedControl)
The goal is to inherit the implementation of the UsersViewControllerBase and customise the segmented items of segmented control in UsersViewController class.
class UsersViewControllerBase: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var tableView: UITableView!
//implementation of delegates
}
class UsersViewController: UsersViewControllerBase {
}
The UsersViewControllerBase is present in the storyboard and all outlets are connected, the identifier is specified.
The question is how can I init the UsersViewController to inherit all the views and functionality of UsersViewControllerBase
When I create the instance of UsersViewControllerBase everything works
let usersViewControllerBase = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("UsersViewControllerBase") as? UsersViewControllerBase
But when I create the instance of UsersViewController I get nil outlets
(I created a simple UIViewController and assigned the UsersViewController class to it in the storyboard )
let usersViewController = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("UsersViewController") as? UsersViewController
It looks like views are not inherited.
I would expect init method in UsersViewControllerBase that gets controller with views and outlets from storyboard:
class UsersViewControllerBase: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var tableView: UITableView!
init(){
let usersViewControllerBase = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("UsersViewControllerBase") as? UsersViewControllerBase
self = usersViewControllerBase //but that doesn't compile
}
}
And I would init UsersViewController:
let usersViewController = UsersViewController()
But unfortunately that doesn't work
When you instantiate a view controller via instantiateViewControllerWithIdentifier, the process is essentially as follows:
it finds a scene with that identifier;
it determines the base class for that scene; and
it returns an instance of that class.
And then, when you first access the view, it will:
create the view hierarchy as outlined in that storyboard scene; and
hook up the outlets.
(The process is actually more complicated than that, but I'm trying to reduce it to the key elements in this workflow.)
The implication of this workflow is that the outlets and the base class are determined by the unique storyboard identifier you pass to instantiateViewControllerWithIdentifier. So for every subclass of your base class, you need a separate storyboard scene and have hooked up the outlets to that particular subclass.
There is an approach that will accomplish what you've requested, though. Rather than using storyboard scene for the view controller, you can instead have the view controller implement loadView (not to be confused with viewDidLoad) and have it programmatically create the view hierarchy needed by the view controller class. Apple used to have a nice introduction to this process in their View Controller Programming Guide for iOS, but have since retired that discussion, but it can still be found in their legacy documentation.
Having said that, I personally would not be compelled to go back to the old world of programmatically created views unless there was a very compelling case for that. I might be more inclined to abandon the view controller subclass approach, and adopt something like a single class (which means I'm back in the world of storyboards) and then pass it some identifier that dictates the behavior I want from that particular instance of that scene. If you want to keep some OO elegance about this, you might instantiate custom classes for the data source and delegate based upon some property that you set in this view controller class.
I'd be more inclined to go down this road if you needed truly dynamic view controller behavior, rather than programmatically created view hierarchies. Or, even simpler, go ahead and adopt your original view controller subclassing approach and just accept that you'll need separate scenes in the storyboard for each subclass.
So, you have your base class:
class UsersViewControllerBase: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var tableView: UITableView!
//implementation of delegates
}
[A] And your subclass:
class UsersViewController: UsersViewControllerBase { var text = "Hello!" }
[B] A protocol that your subclass will be extending:
protocol SomeProtocol {
var text: String? { get set }
}
[C] And some class to handle your data. For example, a singleton class:
class MyDataManager {
static let shared = MyDataManager()
var text: String?
private init() {}
func cleanup() {
text = nil
}
}
[D] And your subclass:
class UsersViewController: UsersViewControllerBase {
deinit {
// Revert
object_setClass(self, UsersViewControllerBase.self)
MyDataManager.shared.cleanup()
}
}
extension UsersViewController: SomeProtocol {
var text: String? {
get {
return MyDataManager.shared.text
}
set {
MyDataManager.shared.text = newValue
}
}
}
To properly use the subclass, you need to do (something like) this:
class TestViewController: UIViewController {
...
func doSomething() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
//Instantiate as base
let usersViewController = storyboard.instantiateViewControllerWithIdentifier("UsersViewControllerBase") as! UsersViewControllerBase
//Replace the class with the desired subclass
object_setClass(usersViewController, UsersViewController.self)
//But you also need to access the property 'text', so:
let subclassObject = usersViewController as! UsersViewController
subclassObject.text = "Hello! World."
//Use UsersViewController object as desired. For example:
navigationController?.pushViewController(subclassObject, animated: true)
}
}
EDIT:
As pointed out by #VyachaslavGerchicov, the original answer doesn't work all the time so the section marked as [A] was crossed out. As explained by an answer here:
object_setClass in Swift
... setClass cannot add instance variables to an object that has already been created.
[B], [C], and [D] were added as a work around. Another option to [C] is to make it a private inner class of UsersViewController so that only it has access to that singleton.
The problem is that you created a scene in the storyboard, but you didn't give the view controller's view any subviews or connect any outlets, so the interface is blank.
If your goal is to reuse a collection of views and subviews in connection with instances of several different view controller classes, the simplest way, if you don't want to create them in code, is to put them in a .xib file and load it in code after the view controller's own view-loading process (e.g. in viewDidLoad).
But if the goal is merely to "customise the segmented items of segmented control" in different instances of this view controller, the simplest approach is to have one view controller class and one corresponding interface design, and perform the customization in code. However, you could load just that segmented control from its own .xib in each case, if it's important to you design it visually.

Delegate property with different type in Swift

Ok so we have UIScrollView declaration:
protocol UIScrollViewDelegate: NSObjectProtocol { ... }
class UIScrollView: UIView {
...
weak var delegate: UIScrollViewDelegate?
...
}
And then UITableView with delegate variant?
protocol UITableViewDelegate: NSObjectProtocol, UIScrollViewDelegate { ... }
class UITableView: UIScrollView {
...
weak var delegate: UITableViewDelegate?
...
}
How Apple did this? When I do my
protocol MyScrollViewSubclassDelegate: NSObjectProtocol, UIScrollViewDelegate { ... }
class MyScrollViewSubclass: UIScrollView {
...
weak var delegate: MyScrollViewSubclassDelegate?
...
}
I get Property 'delegate' with type 'MyScrollViewSubclassDelegate?' cannot override a property with type 'UIScrollViewDelegate?'.
I stumbled upon this a few times and the only work-around I found was just calling my property something else like customDelegate or whatever you like.
It would be neat indeed to be able to just call it delegate but hey!
MyScrollViewSubclass has the delegate property of UIScrollView because it's subclass of UIScrollView.
As delegate is already defined by UIScrollView, you cannot define the same property name with a new type.
Change the variable name delegate to myDelegate (or something else) and it should work.
I got this working but I do not like the solution very much since it throws away type checking.
What I did was this. In my base class I declared the delegate as
weak var delegate: AnyObject? = nil
Then, when I want to call a method on the delegate I do
if let delegate = self.delegate as? MyBaseClassProtocol { delegate.myMethod() }
In my subclass I can then also do the same kind of thing
if let delegate = self.delegate as MySubclassProtocol { delegate.mySubclassMethod() }
As I say, it works, but I don't like it much. Throwing away the typechecking is not to be done lightly in my opinion. I am only sharing in the hope someone with stronger Swift skills can improve upon it, or correct it.