I am new to Swift and am trying to access a 'problemSolved' array that is appended during gameplay in my main GameController class, from another class. For some reason the array is not visible in a UIViewController class where I want to show all the problems solved in a table. I have read many of the Singleton examples on the site to see if this will do it, but that doesn't seem to. Any help or advice here much appreciated!
class GameController: TileDragDelegateProtocol, CenterViewControllerDelegate {
static let sharedInstance = GameController()
var problemsSolved = Array<String>()
func onProblemSolved() {
problemsSolved.append(problem)
println("problemsSolved contains \(problemsSolved)")
}
}
During gameplay I can see in the console that the array is being appended ok in GameController. But when I try to access it in a ViewController class the contents are showing as empty [].
class SidePanelViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
tableView.reloadData()
println("the array viewed from here is empty \(GameController.sharedInstance.problemsSolved)")
}
At the moment I only can imagine that you don't call
GameController.sharedInstance.onProblemSolved()
when you want to append a String to problemsSolved.
You should also consider making your functions and variables in GameController static.
If this doesn't solve your problem I would need more information about how and when you add something to problemsSolved.
Related
I think I'm understanding the weak keyword properly, but wanted to quickly sense check that I'm not doing something wrong.
I'm wanting to store a reference to a specific UITableViewCell so that I can call functions or edit values when needed. Now I don't need to be able to edit the values all the time, only when it's on screen so I don't need to keep a hard copy of the cell at all times. What I've done is as follows:
class customTableViewCell: UITableViewCell {
var variable1: String?
func doAThing(index: Int) {
self.variable1 = "did a thing"
}
}
class MainView: UIView {
weak var cellToManipulate: CustomTableViewCell?
func manipulateCell() {
self.cellToManipulate?.doAThing(index: 1)
}
}
Obviously there will be a tableview and everything else involved, but if I store a weak reference to a cell, it will be removed from memory and set to nil when the tableview scrolls right?
Is there maybe a better way to go about this, aside from forEaching through each item in the visibleCells array?
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.
I know that our IBOutlets should be private, but for example if I have IBOutlets in TableViewCell, how should I access them from another ViewController? Here is the example why I'm asking this kind of question:
class BookTableViewCell: UITableViewCell {
#IBOutlet weak private var bookTitle: UILabel!
}
if I assign to the IBOutlet that it should be private, I got an error in another ViewController while I'm accessing the cell property: 'bookTitle' is inaccessible due to 'private' protection level
If I understand your question correctly, you are supposing the #IBOutlet properties should be marked as private all the time... Well it's not true. But also accessing the properties directly is not safe at all. You see the ViewControllers, TableViewCells and these objects use Implicit unwrapping on optional IBOutlets for reason... You don't need to init ViewController when using storyboards or just when using them somewhere in code... The other way - just imagine you are creating VC programmatically and you are passing all the labels to the initializer... It would blow your head... Instead of this, you come with this in storyboard:
#IBOutlet var myLabel: UILabel!
this is cool, you don't need to have that on init, it will just be there waiting to be set somewhere before accessing it's value... Interface builder will handle for you the initialization just before ViewDidLoad, so the label won't be nil after that time... again before AwakeFromNib method goes in the UITableViewCell subclass, when you would try to access your bookTitle label property, it would crash since it would be nil... This is the tricky part about why this should be private... Otherwise when you know that the VC is 100% on the scene allocated there's no need to be shy and make everything private...
When you for example work in prepare(for segue:) method, you SHOULD NEVER ACCESS THE #IBOutlets. Since they are not allocated and even if they were, they would get overwritten by some internal calls in push/present/ whatever functions...
Okay that's cool.. so what to do now?
When using UITableViewCell subclass, you can safely access the IBOutlets (ONLY IF YOU USE STORYBOARD AND THE CELL IS WITHIN YOUR TABLEVIEW❗️)
and change their values... you see
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// We shouldn't return just some constructor with UITableViewCell, but who cares for this purposes...
guard let cell = tableView.dequeueReusableCell(withIdentifier: "bookTableViewCell", for: indexPath) else { return UITableViewCell() }
cell.bookTitle.text = "any given text" // This should work ok because of interface builder...
}
The above case should work in MVC pattern, not MVVM or other patterns where you don't use storyboards with tableViewControllers and embed cells too much... (because of registering cells, but that's other article...)
I will give you few pointers, how you can setup the values in the cell/ViewController without touching the actual values and make this safe... Also good practice (safety) is to make the IBOutlets optional to be 100% Safe, but it's not necessary and honestly it would be strange approach to this problem:
ViewControllers:
class SomeVC: UIViewController {
// This solution should be effective when those labels could be marked weak too...
// Always access weak variables NOT DIRECTLY but with safe unwrap...
#IBOutlet var titleLabel: UILabel?
#IBOutlet var subtitleLabel: UILabel?
var myCustomTitle: String?
var myCustomSubtitle: String?
func setup(with dataSource: SomeVCDataSource ) {
guard let titleLabel = titleLabel, let subtitleLabel = subtitleLabel else { return }
// Now the values are safely unwrapped and nothing can crash...
titleLabel.text = dataSource.title
subtitleLabel.text = dataSource.subtitle
}
// WHen using prepare for segue, use this:
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.text = myCustomTitle
subtitleLabel.text = myCustomSubtitle
}
}
struct SomeVCDataSource {
var title: String
var subtitle: String
}
The next problem could be this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destinationVC = segue.destination as? SomeVC else { return }
let datasource = SomeVCDataSource(title: "Foo", subtitle: "Bar")
// This sets up cool labels... but the labels are Nil before the segue occurs and even after that, so the guard in setup(with dataSource:) will fail and return...
destinationVC.setup(with: datasource)
// So instead of this you should set the properties myCustomTitle and myCustomSubtitle to values you want and then in viewDidLoad set the values
destinationVC.myCustomTitle = "Foo"
destinationVC.myCustomSubtitle = "Bar"
}
You see, you don' need to set your IBOutlets to private since you never know how you will use them If you need any more examples or something is not clear to you, ask as you want... Wish you happy coding and deep learning!
You should expose only what you need.
For example you can set and get only the text property in the cell.
class BookTableViewCell: UITableViewCell {
#IBOutlet weak private var bookTitleLabel: UILabel!
var bookTitle: String? {
set {
bookTitleLabel.text = newValue
}
get {
return bookTitleLabel.text
}
}
}
And then, wherever you need:
cell.bookTitle = "It"
Now outer objects do not have access to bookTitleLabel but are able to change it's text content.
What i usually do is configure method which receives data object and privately sets all it's outlets features.
I haven't come across making IBOutlets private to be common, for cells at least. If you want to do so, provide a configure method within your cell that is not private, which you can pass values to, that you want to assign to your outlets. The function within your cell could look like this:
func configure(with bookTitle: String) {
bookTitle.text = bookTitle
}
EDIT: Such a function can be useful for the future, when you change your cell and add new outlets. You can then add parameters to your configure function to handle those. You will get compiler errors everywhere, where you use that function, which allows you to setup your cell correctly wherever you use it. That is helpful in a big project that reuses cells in different places.
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.
Here is my class:
class ViewController: UIViewController {
var myArray : NSArray!
}
I want to fire an event every time myArray points to a new array, like this:
self.myArray = ["a"]
self.myArray = ["b"]
I've tried rx_observe but failed, here is my code:
self.rx_observe(NSArray.self, "myArray").subscribeNext { (array) -> Void in
print(array)
}
It only fires the first time, what's the problem?
Most of the time, if you have control of the backing variable, you would prefer Variable to using rx_observe.
class ViewController: UIViewController {
var myArray : Variable<NSArray>!
}
The first time you'll use myArray, you would asign it like so
myArray = Variable(["a"])
Then, if you want to change its value
myArray.value = ["b"]
And you can easily observe its changes, using
myArray.asObservable().subscribeNext { value in
// ...
}
If you really want to use rx_observe (maybe because the variable is used elsewhere in your program and you don't want to change the API of your view controller), you would need to declare myArray as dynamic (another requirement is that the hosting class is a child of NSObject, here UIViewController satisfies this requirement). KVO is not implemented by default in swift, and using dynamic ensures access is done using the objective-c runtime, where KVO events are handled.
class ViewController: UIViewController {
dynamic var myArray: NSArray!
}
Documentation for this can be found here