Class Variable not Being Set - swift

This is a weird one. I have several classes so far in a test app and everything has been going swimmingly. However, I'm getting the error EpisodePlayerController.Type does not have a member named episodeData when I declare otherThing below.
import UIKit
class EpisodePlayerController: UIViewController {
var episodeData = "Hi"
var otherThing = episodeData
}
Tried restarting Xcode, restarting Mac, recreating the class, renaming the class, etc. At a loss. Might be a bug in my Xcode install, but I'd love to be wrong and not have wait for another release. ;)

This seems to solve the problem:
class EpisodePlayerController: UIViewController {
var episodeData = "Hi"
var otherThing = ""
init(){
otherThing = episodeData
}
}
Take a look at the property reference.

The problem is that as far as Swift is concerned, none of your properties have values until the class is fully initialized. Before that point, you can't interact with 'self' implicitly. (this includes its properties and functions.) I think your class is a good candidate for the #lazy attribute:
import UIKit
class EpisodePlayerController: UIViewController {
var episodeData: String = "Hi"
#lazy var otherData: String = self.episodeData
}
You could also include more complex stuff if you need to:
import UIKit
class ClassTwo {
var episodeData = "Hi"
#lazy var otherData: String = {
// Additional parameters
return self.episodeData
}()
}
This way, you can access self because the property 'otherData' won't be called until 'episodeData' is fully initialized.

Because this view was being called via a segue, I wound up making the variable optional and then calling a function from the segue to set the optional variable and then trigger any function dependent on that variable:
import UIKit
class YourController: UIViewController {
var yourData:NSString?
func setYourData(value:NSString) {
self.yourData = value
dependentFunction()
}
A similar effect can be achieved using the didSet and/or willSet methods:
import UIKit
class YourController: UIViewController {
var yourData:NSString? {
didSet {
// Dependent functionality
}
}
func setYourData(value:NSString) {
self.yourData = value
}
Also, for reference, the segue looks like this:
func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if segue.identifier == "yourSegue" {
var destination:YourController = segue.destinationViewController as YourController
destination.setYourData(someData)
}
}

i think this code will help.
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()”

Related

Accessing Class var on AnyClass variable in Swift 4

In Swift 3.2 this (let id = row.tableViewCellClass?.reuseIdentifier) worked:
class DrillDownTableViewCell {
class var reuseIdentifier: String
{
return String(describing: self)
}
}
class RowViewModel: NSObject
{
var tableViewCellClass: AnyClass?
}
class Foo {
var row : RowViewModel?
func setup() {
row = RowViewModel()
row?.Class = DrillDownTableViewCell.self
}
func doThings() {
let id = row?.tableViewCellClass?.reuseIdentifier
}
}
After my Swift 4 update, it's showing "Instance member 'reuseIdentifier' cannot be used on type 'AnyObject'.
How would I access a class variable on a class who's metaType information is stored in an AnyClass variable?
(I assume you mean to have a ? after row in doThings(). I assume that's a typo and not part of the question. There are several other ? missing here and some other typos that I won't dive into.)
If you expect tableViewCellClass to have a reuseIdentifier class property, then it isn't of type AnyClass. There are many classes that don't have that property. You want classes that conform to a protocol:
protocol Identifiying {
static var reuseIdentifier: String { get }
}
So your model requires an Identifying class:
class RowViewModel: NSObject {
var tableViewCellClass: Identifiying.Type?
}
Then you can use this as you're expecting.

Testing Delegation in Playground giving 'nil'

I have the following code in Playground -I'm learning delegation-...
import UIKit
protocol FollowThisProtocol {
func passingTheValue(aValue: String)
}
class IPassTheValues{
var aDelegate: FollowThisProtocol!
func runThisFunc(){
aDelegate.passingTheValue(aValue: "I like this game")
}
}
class IReceiveTheValues: FollowThisProtocol{
var localString: String!
var instanceOfClass: IPassTheValues!
func runReceivefunc(){
instanceOfClass.aDelegate = self
}
func passingTheValue(aValue: String) {
localString = aValue
}
}
When I attempt to
print(IReceiveTheValues().localString)
it's giving me nil
It also gives me nil if I run the following lines before attempting to print(IReceiveTheValues().localString)...
IPassTheValues()
IReceiveTheValues()
could you please help me understand why the value is not being passed from the 1st class to the 2nd..?
Or if you can spot something in my code that is contradicting itself, could you please point it out..?
Appreciate your time and help.
You need to create the IPassTheValues object before assigning yourself as the delegate, and then call runThisFunc() on the instance:
func runReceivefunc(){
instanceOfClass = IPassTheValues()
instanceOfClass.aDelegate = self
instanceOfClass.runThisFunc()
}
Then test:
// Create the `IReceiveTheValues` object
let irtv = IReceiveTheValues()
// Run the method
irtv.runReceivefunc()
// Get the resulting string
print(irtv.localString)
I suggest 2 other changes. Make your delegate weak so that you don't get a retain cycle which makes it impossible to delete either object. In order to do that, you will need to add : class to your protocol declaration because only reference objects (instances of a class) can be weak.
Here's the modified code. Try it and see what happens when you delete weak.
protocol FollowThisProtocol: class {
func passingTheValue(aValue: String)
}
class IPassTheValues{
weak var aDelegate: FollowThisProtocol!
func runThisFunc(){
print("Calling delegate...")
aDelegate.passingTheValue(aValue: "I like this game")
}
deinit {
print("IPassTheValues deinitialized")
}
}
class IReceiveTheValues: FollowThisProtocol{
var localString: String!
var instanceOfClass: IPassTheValues!
func runReceivefunc(){
instanceOfClass = IPassTheValues()
instanceOfClass.aDelegate = self
instanceOfClass.runThisFunc()
}
func passingTheValue(aValue: String) {
print("Receiving value from helper object...")
localString = aValue
}
deinit {
print("IReceiveTheValues deinitialized")
}
}
func test() {
let irtv = IReceiveTheValues()
irtv.runReceivefunc()
print(irtv.localString)
}
test()

Using NSTreeController with NSOutlineView

I'm trying (unsuccessfully) to build a TreeController-controlled NSOutlineView. I've gone through a bunch of tutorials, but they all pre-load the data before starting anything, and this won't work for me.
I have a simple class for a device:
import Cocoa
class Device: NSObject {
let name : String
var children = [Service]()
var serviceNo = 1
var count = 0
init(name: String){
self.name = name
}
func addService(serviceName: String){
let serv = "\(serviceName) # \(serviceNo)"
children.append(Service(name: serv))
serviceNo += 1
count = children.count
}
func isLeaf() -> Bool {
return children.count < 1
}
}
I also have an even more simple class for the 'Service':
import Cocoa
class Service: NSObject {
let name: String
init(name: String){
self.name = name
}
}
Finally, I have the ViewController:
class ViewController: NSViewController {
var stepper = 0
dynamic var devices = [Device]()
override func viewDidLoad() {
super.viewDidLoad()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func addDeviceAction(_ sender: Any) {
let str = "New Device #\(stepper)"
devices.append(Device(name: str))
stepper += 1
print("Added Device: \(devices[devices.count-1].name)")
}
#IBAction func addService(_ sender: Any) {
for i in 0..<devices.count {
devices[i].addService(serviceName: "New Service")
}
}
}
Obviously I have 2 buttons, one that adds a 'device' and one that adds a 'service' to each device.
What I can't make happen is any of this data show up in the NSOutlineView. I've set the TreeController's Object Controller Property to Mode: Class and Class: Device, and without setting the Children, Count, or Leaf properties I get (predictably):
2017-01-04 17:20:19.337129 OutlineTest[12550:1536405] Warning: [object class: Device] childrenKeyPath cannot be nil. To eliminate this log message, set the childrenKeyPath attribute in Interface Builder
If I then set the Children property to 'children' things go very bad:
2017-01-04 17:23:11.150627 OutlineTest[12695:1548039] [General] [ addObserver:forKeyPath:options:context:] is not supported. Key path: children
All I'm trying to do is set up the NSOutlineView to take input from the NSTreeController so that when a new 'Device' is added to the devices[] array, it shows up in the Outline View.
If anyone could point me in the right direction here I'd be most grateful.
Much gratitude to Warren for the hugely helpful work. I've got it (mostly) working. A couple of things that I also needed to do, in addition to Warren's suggestions:
Set the datastore for the Tree Controller
Bind the OutlineView to the TreeController
Bind the Column to the TreeController
Bind the TableView Cell to the Table Cell View (yes, really)
Once all that was done, I had to play around with the actual datastore a bit:
var name = "Bluetooth Devices Root"
var deviceStore = [Device]()
#IBOutlet var treeController: NSTreeController!
#IBOutlet weak var outlineView: NSOutlineView!
override func viewDidLoad() {
super.viewDidLoad()
deviceStore.append(Device(name: "Bluetooth Devices"))
self.treeController.content = self
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func addDeviceAction(_ sender: Any) {
if(deviceStore[0].name == "Bluetooth Devices"){
deviceStore.remove(at: 0)
}
Turns out the Root cannot be child-less at the beginning, at least as far as I can tell. Once I add a child, I can delete the place-holder value and the tree seems to work (mostly) as I want. One other thing is that I have to reload the data and redisplay the outline whenever the data changes:
outlineView.reloadData()
outlineView.setNeedsDisplay()
Without that, nothing. I still don't have the data updating correctly (see comments below Warren's answer) but I'm almost there.
To state the obvious, a NSTreeController manages a tree of objects all of which need to answer the following three questions/requests.
Are you a leaf i.e do you have no children? = leafKeyPath
If you are not a leaf, how many children do you have ? = countKeyPath
Give me your children! = childrenKeyPath
Its simple to set these up in IB or programatically. A fairly standard set of properties is respectively.
isLeaf
childCount
children
But its totally arbitrary and can be any set of properties that answer those questions.
I normally set up a protocol named something like TreeNode and make all my objects conform to it.
#objc protocol TreeNode:class {
var isLeaf:Bool { get }
var childCount:Int { get }
var children:[TreeNode] { get }
}
For your Device object you answer 2 out 3 question with isLeaf and children but don't answer the childCount question.
Your Device's children are Service objects and they answer none of that which is some of the reason why you are getting the exceptions.
So to fix up your code a possible solution is ...
The Service object
class Service: NSObject, TreeNode {
let name: String
init(name: String){
self.name = name
}
var isLeaf:Bool {
return true
}
var childCount:Int {
return 0
}
var children:[TreeNode] {
return []
}
}
The Device object
class Device: NSObject, TreeNode {
let name : String
var serviceStore = [Service]()
init(name: String){
self.name = name
}
var isLeaf:Bool {
return serviceStore.isEmpty
}
var childCount:Int {
return serviceStore.count
}
var children:[TreeNode] {
return serviceStore
}
}
And a horrible thing to do from a MVC perspective but convenient for this answer. The root object.
class ViewController: NSViewController, TreeNode {
var deviceStore = [Device]()
var name = "Henry" //whatever you want to name your root
var isLeaf:Bool {
return deviceStore.isEmpty
}
var childCount:Int {
return deviceStore.count
}
var children:[TreeNode] {
return deviceStore
}
}
So all you need to do is set the content of your treeController. Lets assume you have an IBOutlet to it in your ViewController.
class ViewController: NSViewController, TreeNode {
#IBOutlet var treeController:NSTreeController!
#IBOutlet var outlineView:NSOutlineView!
override func viewDidLoad() {
super.viewDidLoad()
treeController.content = self
}
Now each time you append a Device or add a Service just call reloadItem on the outlineView (that you also need an outlet to)
#IBAction func addDeviceAction(_ sender: Any) {
let str = "New Device #\(stepper)"
devices.append(Device(name: str))
stepper += 1
print("Added Device: \(devices[devices.count-1].name)")
outlineView.reloadItem(self, reloadChildren: true)
}
Thats the basics and should get you started but the docs for NSOutlineView & NSTreeController have a lot more info.
EDIT
In addition to the stuff above you need to bind your outline view to your tree controller.
First ensure your Outline View is in view mode.
Next bind the table column to arrangedObjects on the tree controller.
Last bind the text cell to the relevant key path. In your case it's name. objectValue is the reference to your object in the cell.

Swift Function Algorithm

when I run this method in my project, the "name" variable is empty
Example :
import UIKit
class userDetail {
static var name : String?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func OK(sender: UIButton){
getUserDetail()
print(userDetail.name)
// The first click is nil, the second click is print "test". How can I solve this problem?
}
func getUserDetail() {
userDetail.name = "test"
}
}
You have to instantiate it, as most of the comments suggest.
You need to do:
var userInfo = userDetail()
userInfo.name = "Test"
print(userInfo.name!)
As others suggested, you need to make class names (userDetail) capital. This is because class names start with class names. (UserDetail)

variable cannot be modified in another class

am trying to modify a variable in another class, but it doesn't change.
class ViewController: UIViewController {
var t = 1
override func viewDidLoad() {
super.viewDidLoad()
t = 9
var pp = action().test()
println(pp) // got “2” here
}
the above should print "10" , but it shows "2".
another swift file:
class action {
var k = ViewController().t
func test()->Int{
k++
return k
}
}
Anything I made wrong?
Thnaks.
When you have a class with properties, and create an instance of that class, properties are bound to the class instance and not the class type. So if you create 2 instances of the same class their properties are independent, so if you change a property in one instance, that won't affect the same property in the other instance.
Note: by convention, in swift type names always start in uppercase, so I have renamed action to Action in my code.
Your code isn't working as expected because in the Action class you are creating a new instance of ViewController:
var k = ViewController().t
which has no relationship with the instance used to instantiate Action - so the new instance will have its t property set to 1.
The correct way to fix it is to pass the view controller instance to Action, and let it work on that instance.
class ViewController: UIViewController {
var t = 1
override func viewDidLoad() {
super.viewDidLoad()
t = 9
var action = Action(viewController: self)
var pp = action.test()
println(pp) // got “2” here
}
}
class Action {
var k: Int
init(viewController: ViewController) {
self.k = viewController.t
}
func test()->Int{
k++
return k
}
}
The above code should give an indication of what's wrong with your code, but it can be written in a better way. Action doesn't really need the ViewController instance, it just needs an integer passed in to its initializer, so a better way to achieve the same result is by modifying the code as follows:
class ViewController: UIViewController {
var t = 1
override func viewDidLoad() {
super.viewDidLoad()
t = 9
var action = Action(t: self.t)
var pp = action.test()
println(pp) // got “2” here
}
}
class Action {
var k: Int
init(t: Int) {
self.k = t
}
func test()->Int{
k++
return k
}
}