Sending a variable to be modified in another class? - ios

How can one send a variable from a viewcontroller to be modified by another viewcontroller?
I've tried setting the variable in the performSegue, but it does not get modified.
Sample code:
class VC1: ViewController{
var myVar: MyVar
....
prepare(for segue:...) {
let nextVC = segue.destination as! VC2
nextVC.var = myVar
}
....
}
class VC2: ViewController {
var var: MyVar
....
override func viewDidLoad() {
super.viewDidLoad()
var = MyVar("newValue")
}
}
After this code is executed, the value of myVar in VC1 is not changed to the new value. I believe it only gets a shallow copy of myVar, not a deep copy.
Is what I want achievable in swift?

Classes in Swift are pass by reference, whereas structs are pass by value. Assuming that MyVar is a class, you need to modify the properties of the class, ie:
myVar.property = "xyz"
instead of defining a new instance of the class as you have done in your question.

When you set var = MyVar("newValue") it assign new instance to var.

Examine the results from the following code in a playground. It should give you more insight into what you should expect, without the complication of segues and controllers.
class Variable {
var name:String
init(_ nameString:String) {
name = nameString
}
}
class Sender {
var myVar = Variable("Start name")
func showChanges() {
let receiver = Receiver(myVar)
print(myVar.name)
receiver.modify()
print(myVar.name)
receiver.replace()
print(myVar.name)
}
}
class Receiver {
var received: Variable
init(_ variable:Variable) {
received = variable
}
func modify() {
received.name = "Changed name"
}
func replace() {
received = Variable("New variable")
}
}
let s = Sender()
s.showChanges()

Related

Swift - Passing custom objects as a parameter

I started learning Swift today and in my first test app I am getting this error:
TestClass is not convertible to AnotherClass
The following is the TestClass:
class TestClass : NSObject {
var parameter1 : String = ""
var parameter2 : String = ""
override init() {
super.init()
}
func createJob(parameter1: String, parameter2: String) -> TestClass {
self.parameter1 = parameter1
self.parameter2 = parameter2
return self;
}
}
And this is the AnotherClass:
class AnotherClass: NSObject {
private struct internalConstants {
static let test1 = "testData"
static let test2 = "testData2"
}
var current : String
override init() {
self.current = internalConstants.test1
super.init()
}
func executeTask(testClass : TestClass) {
if testClass.parameter1 == "abc" {
return;
}
}
}
And this is the ViewController where I am getting the compiler error:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let obj = TestClass()
AnotherClass.executeTask(obj)
}
}
AnotherClass.executeTask line is giving the compiler error.
The obj variable sent as a parameter on this line is highlighted by Xcode with the error
"TestClass is not convertible to AnotherClass".
In C# or Objective C it is allowed to pass custom objects as a parameter to another methods. How can I do it in Swift?
Let's correct first the TestClass. This is how you should init a class like that:
class TestClass : NSObject {
....
init(parameter1: String, parameter2: String) {
....
}
}
Much simpler. Now, going back to your problem,
"TestClass is not convertible to AnotherClass".
Take a look at it again. The line you've mentioned in your question. You are trying to do this:
let obj = TestClass()
AnotherClass.executeTask(obj)
This line, AnotherClass.executeTask(obj), is giving you an error because indeed executeTask() is an instance method. You could do three ways for that.
add static keyword to the func executeTask... So it becomes like this: static func executeTask(testClass : TestClass) {
Instead of static keyword, you could add class. It becomes like so: class func executeTask(....
OR, better if you just instantiate the AnotherClass. Make a new object of AnotherClass. How to instantiate? You tell me. But here:
let anotherClass = AnotherClass()
Either implement executeTask as a class function
class func executeTask(testClass : TestClass) {
if testClass.parameter1 == "abc" {
return;
}
}
or instantiate AnotherClass in vieweDidLoad
let obj = TestClass()
let another = AnotherClass()
another.executeTask(testClass: obj)
Note the slightly different call to executeTask with the argument name.
And there is really no reason for you to subclass NSObject as I see it.
I think it's best to keep is simple. Create an instance of AnotherClass inside of ViewController.
class ViewController: UIViewController {
// Create an instance of AnotherClass which lives with ViewController.
var anotherClass = AnotherClass()
override func viewDidLoad() {
super.viewDidLoad()
let obj = TestClass()
// Use the instance of AnotherClass to call the method.
anotherClass.executeTask(testClass: obj)
}
}

How to send multiple variables through segue

How can I send multiple variables through a segue in Swift? The QBBust gets sent over fine and prints on the view controller, but the QBName doesn't get sent over for some reason. Can anyone spot why?
if let send = sender as? Double{
destination.QBBust = send
}
if let sent = sender as? String{
destination.QBName = sent
}
}
}
private var _QBName:String!
var QBName: String{
get{
return _QBName
} set {
_QBName = newValue
}
}
private var _QBBust:Double!
var QBBust: Double {
get {
return _QBBust
} set{
_QBBust = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
let bust = String(Int(_QBBust))
QBBustLabel.text = "\(bust)%"
QBNameLabel.text = _QBName
}
This next part is in the button function that triggers the segue
performSegue(withIdentifier: "QBResultVC", sender: QBBust)
performSegue(withIdentifier: "QBResultVC", sender: QBName)
As in Tiago's answer, you can create a new struct or class which has QBName and QBBust properties. In addition, you can also use tuple in Swift.
This is an example:
in Destination ViewController
declare var QBInfo:(name: String, bust: Double)?
and in the button function that triggers the segue
let QBInfo = (name: QBName, bust: QBBust)
performSegue(withIdentifier: "QBResultVC", sender: QBBust)
then in prepareForSegue:sender:method
destination.QBInfo = QBInfo
This question is duplicate, but you can create a Struct or new Class, and storage your data how properties and send the 'transport' object in segue.
For detail, look this answser:
Swift sending Multiple Objects to View Controller

Pass array to singleton (swift)

class MySingleton{
static let shareInstance = MySingleton()
private init() {}
var myDetail = [Detail]()
}
class DetailTableViewController {
var expense = [Detail]()
override func viewDidLoad() {
super.viewDidLoad()
... put stuff in expense array ....
MySingleton.shareInstance.myDetail = expense //<--- doesn't work
// error is "cannot assign value of type '[Detail]' to type [MySingleton.Detail]"
}
}
How do I copy an array to my MySingleton?
right now i just pass my array around my classes using segue
From your error, it is likely you are defining Detail twice, once locally to the singleton, once globally for the viewController.

Swift Delegate in own class

I am currently trying to write my first Swift Mac application. Currently I have hard times refactoring some code into another class.
Current Status:
import Cocoa
class TestClass: NSObject, NSTextStorageDelegate {
#IBOutlet var codeTextView: NSTextView!
var syntaxParser:TRexSyntaxKitParser?
var textStorage : NSTextStorage!
init(syntaxParser:TRexSyntaxKitParser, textView:NSTextView) {
self.syntaxParser = syntaxParser
super.init()
if let textViewStorage = textView.textStorage {
self.textStorage = textViewStorage
self.textStorage.delegate = self
}
}
func textStorageDidProcessEditing(notification: NSNotification) {
let inputString = self.textStorage.string
let wholeRange = NSMakeRange(0, count(inputString))
self.textStorage.removeAttribute(NSForegroundColorAttributeName, range:wholeRange)
let attributes = self.syntaxParser!.parse(inputString)
print("Attributes: \(attributes)")
for attribDict: [String:AnyObject] in attributes {
let range = NSMakeRange(attribDict["rangeStart"] as! Int, attribDict["rangeLength"] as! Int)
self.textStorage.addAttribute(attribDict["attributeKey"] as! String, value:NSColor(hexString: attribDict["color"] as! String)!, range:range)
}
}
}
and this is how i call this class:
import Cocoa
class CodeEditorViewController: NSViewController {
#IBOutlet var codeTextView: NSTextView!
var syntaxParser:TRexSyntaxKitParser?
override func viewDidLoad() {
super.viewDidLoad()
self.syntaxParser = TRexSyntaxKitParser(language:"latex",theme:"classic")
let testClass = TestClass(syntaxParser: self.syntaxParser!, textView: self.codeTextView)
codeTextView.lnv_setUpLineNumberView()
}
but this produces the following error:
[NSFont textStorageDidProcessEditing:]: unrecognized selector sent to instance
I do not see where I would call the delegate method from NSFont ?
So to be precise: How can I refactor the first class into two different one ?
Think about the memory management of this line:
let testClass = TestClass(syntaxParser: self.syntaxParser!, textView: self.codeTextView)
testClass is a local variable. So what happens to your brand new TestClass instance? It comes into existence and immediately vanishes in a puff of smoke when viewDidLoad comes to an end.
Thus, you now have a delegate pointing at an object that does not exist. Hence, the crash.
Solution: make testClass something that will persist long enough to do you some good - like, an instance property of your view controller. That will give you exactly the refactoring you are after (this is a standard design pattern).

Achieve dynamic initialisation based on Class passed as parameter in Swift

I would like to create objects based on their class passed into a function.
First, I have an interface every generatable object should conform to:
interface Generatable{
init(raw: NSDictionary)
}
and a function that would take the class as a parameter
func generateDynamicObjectFromClass(generatable: Generatable.Type){
var someJSONData : NSDictionary = NSDictionary()
var myGeneratedObject : Generatable = generatable(raw: someJSONData) //custom initialiser of Generatable class
}
and then, call it like that:
generateDynamicObjectFromClass(MyGeneratableObject.Type)
MyGeneratableObject class
class MyGeneratableObject : NSObject, Generatable{
init(raw: NSDictionary){
//some initialisation
}
}
However, MyGeneratableObject does not have a Type property, so the problem is to get the corresponding class of the underlying object during runtime. Is that possible ?
You have to define generateDynamicObjectFromClass as a generic function:
protocol Generatable {
init(raw: NSDictionary)
}
func generateDynamicObjectFromClass<T where T:Generatable>(generatable:T.Type, otherParam: NSString = "") -> T {
var someJSONData : NSDictionary = NSDictionary()
var myGeneratedObject = T(raw: someJSONData)
return myGeneratedObject
}
class MyGeneratableObject : NSObject, Generatable {
init(raw: NSDictionary){
println("MyGeneratableObject init")
}
}
var myObject1 = generateDynamicObjectFromClass(MyGeneratableObject.self, otherParam: "foo")
var myObject2 = generateDynamicObjectFromClass(MyGeneratableObject.self)
Alternatively, you can create the object as
var myObject = MyGeneratableObject(raw: NSDictionary())
without the need for a separate function.

Resources