I don't understand why I could not find this question somewhere as I think it's a pretty common one so maybe I'm not well awake. Sorry for that if it's the case.
I have my prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) function and I cannot find out what class is sender. I don't want to try every classes of the Foundation framework so is there a way to know it at runtime.
When I use the debugger I only get (AnyObject!) sender = (instance_type = Builtin.RawPointer = ... which doesn't really help.
Instead of asking the object for its class, I find it more "Swifty" to use "if let" to check if it is what I am looking for.
func inputUnknown(sender : AnyObject) {
if let tableView = sander as? UITableView {
// now you have an object with a defined class
}
else {
// check for other classes or proceed with a default action
}
}
Every object has an underlying dynamicType property, which, in theory, should reveal the actual type of the object, when printed to the console. However, Swift doesn't yet have support for printable metatypes, so you'll get ExistentialMetatype for an instance's dynamicType and MetaType for a class's type. In order to get around this, you can cast your Type variable to AnyObject, which, when printed to your console will print the Objective C metatype.
if let object = sender {
println(object.dynamicType as AnyObject)
}
Related
I have in in my app an function. Is there a way to transfer it to other Viewcontroller? if I use UserDefaults.standard.set(function(), forKey: "function")
I don't know how to load it, because
let function() = UserDefaults.standard.object(forKey: "function") as? CGFunction
doesn't work.
Thanks for answers!
Passing and returning functions
The following function is returning another function as its result which can be later assigned to a variable and called.
func jediTrainer () -> ((String, Int) -> String) {
func train(name: String, times: Int) -> (String) {
return "\(name) has been trained in the Force \(times) times"
}
return train
}
let train = jediTrainer()
train("Obi Wan", 3)
Yes, Swift allows you to pass functions or closures to other objects, since functions and closures are themselves first class objects.
However, you cannot save a function or closure to UserDefaults. To the best of my knowledge there is no way to serialize functions or closures, and in any case they certainly are not one of the very small list of types that can be saved to UserDefaults. (Known as "property list objects" since the same small set of types can be store to both property lists and to UserDefaults.)
Why do you want to pass a function to another view controller?
In Swift, functions are Closures. You can simply pass closures in code.
Class A {
var someFunction: (Int) -> String
init(f: (Int) -> String) {
someFunction = f
}
}
func f2(a: Int) -> String {
return "Value is: \(a)"
}
let AInstance = A(f: f2)
print(AInstance.someFunction(5)) // prints "Value is: 5"
or specific someFunction as optional like var someFunction: ((Int) -> String)! and set it later in code.
I'm going to answer your initial question:
I have in in my app an function. Is there a way to transfer it to other Viewcontroller?
Yes, there is a way: use your segue rather than trying to store the function in userDefaults.
1) Make sure that the destination view controller has an instance variable that can hold your function. (Note that in Swift 4, you'll have to make sure you either set a default value for that variable, or create a custom initializer to ensure the variable is given a value on initialization.)
2) In the first view controller, wherever you handle your segue, instantiate your destination view controller. Then set the variable to your function. (You can do this, for example, in an override of the prepare(for segue: UIStoryboardSegue, sender: Any?) method.)
I searched to solving this problem a lot of questions but no one helped me.
Have 2 viewControllers and I need to send array from one to another.
Calling method in the first viewController:
SubmitViewController.acceptContent(content)
Accepting in other:
var contentAccepted = [String]()
class func acceptContent(content: [String]){
contentAccepted = content
}
The problem is in taking the mistake on contentAccepted line: Instance member cannot be used on type UIViewController
You are referring to self within your type-method.
Here is what apple says on that matter:
Within the body of a type method, the implicit self property refers to
the type itself, rather than an instance of that type. For structures
and enumerations, this means that you can use self to disambiguate
between type properties and type method parameters, just as you do for
instance properties and instance method parameters.
More generally, any unqualified method and property names that you use
within the body of a type method will refer to other type-level
methods and properties. A type method can call another type method
with the other method’s name, without needing to prefix it with the
type name. Similarly, type methods on structures and enumerations can
access type properties by using the type property’s name without a
type name prefix.
More info on type-methods (class-methods) can be found here
Try to do it that way:
var contentAccepted = [String]()
class func acceptContent(content: [String]){
ViewController().contentAccepted = content
}
Make sure that you really need type-method.
The issue you are running into is that contentAccepted is an instance variable (as it should be) and acceptContent() is a class method. So, your method can't get to your variable. You will need to create an instance of SubmitViewController before you are able to access its variables.
let submitViewController = SubmitViewController()
submitViewController.contentAccepted = content
By first creating a SubmitViewController instance, you can now access that instance variable.
If you are passing information from one View Controller to another in the midst of a segue, you might consider using prepareForSegue.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Submit" {
if let view = segue.destinationViewController as? SubmitViewController {
view.contentAccepted = content // If content is an instance variable
}
}
}
I have a feeling there is more than one problem with this code, but my first issue is that my delegate returns nil and I do not know why. First, is my delegate:
import UIKit
//delegate to move information to next screen
protocol userEnteredDataDelegate {
func userDidEnterInformation(info:NSArray)
}
Next, I have a var defined for the delegate and I believe the ? makes it an optional variable? This is defined inside the class
var dataPassDelegate:userEnteredDataDelegate? = nil
Now, after my user has entered information into the fields in the view, I want to add those field values to an array and then pass that array on to the next view where it will be added to. I have pieced this code together from some YouTube examples but I think I am missing a needed part. When do I assign some kind of value to the dataPassDelegate var so it is not nil when the if statement comes? Do I even need that if statement?
if blankData != 1 {
//add code to pass data to next veiw controller
enteredDataArray = [enterDate.text, enterSeason.text, enterSport.text, enterDispTo.text]
//println(enteredDataArray)
self.appIsWorking ()
if (dataPassDelegate != nil) {
let information: NSArray = enteredDataArray
println(information)
dataPassDelegate!.userDidEnterInformation(information)
self.navigationController?.popViewControllerAnimated(true)
} else {
println ("dataPassDelegate = nil")
}
//performSegueWithIdentifier("goToDispenseScreenTwo", sender: self)
activityIndicator.stopAnimating()
UIApplication.sharedApplication().endIgnoringInteractionEvents()
}
blankData = 0
}
Your help is appreciated.
A delegate is a pointer to another object that conforms to a particular protocol. Often you use delegates to refine the behavior of your class, or to send back status information o the results of an async network request
When you set your dataPassDelegate delegate is up to you.
What is the object that has the dataPassDelegate property? What object will be serving as the delegate?
You need to create 2 objects (the object that will be serving as the delegate, and the object that has the dataPassDelegate property) and link them up.
We can't tell you when to do that because we don't know what you're trying to do or where these objects will be used.
For some reason I am getting this error when the performSegueWithIdentifier line is reached.
I have this code:
if let storedAPIKeychain: AnyObject = dictionary.objectForKey("api_key") {
println(storedAPIKeychain)
//This is the line that causes the problems.
performSegueWithIdentifier("skipBrandSegue", sender: self)
}
The println() works fine and outputs the correct information.
I am trying to pass the storedAPIKeychain along with the segue:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if segue.identifier == "skipBrandSegue" {
// Create a new variable to store the instance of the next view controller
let destinationVC = segue.destinationViewController as brandsViewController
destinationVC.storedAPIKey = storedAPIKeychain!
}
}
Which I thought might have been the problem. however when I changed that line to:
destinationVC.storedAPIKey = "someAPIplaceholder"
I also get the same error.
Can someone please advise me what this error is and how to resolve it. Thanks.
Edit: Screenshot of error:
The dynamic cast class unconditional indicates that a forced cast failed, because a variable cannot be cast to another type.
In your code I see one cast only at this line:
let destinationVC = segue.destinationViewController as brandsViewController
which means the destination view controller is not an instance of brandsViewController.
To fix the issue:
check in interface builder that the custom class property for the destination view controller is correctly set to brandsViewController
check that the segue is actually pointing to that view controller
If none of the above fixes the problem, set a breakpoint at that line and inspect the actual type of the destination view controller.
Side note: by convention, in swift all type names start with uppercase letter, whereas functions, variables and properties with lower case. If you want to make your code readable to other swift developers, I suggest you to stick with that convention (rename brandsViewController as BrandsViewController)
#antonios answer should solve your problem. The break is due to the object not being cast (found and assigned).
Just a side note: you're going to have a few issues with this line:
if let storedAPIKeychain: AnyObject = dictionary.objectForKey("api_key")
especially if you're expecting to get a String from it and pass that between ViewControllers?
Cast it as a String, Create a global scope variable and then assign it to that variable to use - Will be much easier to handle then.
var globalVariable = "" //add this line at the top, just before your class declaration.
if let storedAPIKeychain = dictionary.objectForKey("api_key") as? String {
self.globalVariable = storedAPIKeychain
}
I came across a strange behaviour in Swift while programming a Master-Detail application.
Here's the scenario:
It's a simple Task Manager application. I have two text controls (TaskName, TaskDescription) on the TaskDetailView and two string variables with the same name but in lowerCamelCase (taskName, taskDescription) declared in the TaskDetailViewController.
#IBOutlet var TaskName:UITextField! //UpperCamelCase
#IBOutlet var TaskDescription:UITextView! //UpperCamelCase
var taskName:String? //lowerCamelCase
var taskDescription:String? //lowerCamelCase
I am setting the values of Text controls on ViewDidLoad() as usual:
override func viewDidLoad() {
super.viewDidLoad()
TaskName.text = taskName
TaskDescription.text = taskDescription
}
And I am passing the data in prepareForSegue (from TaskListViewController) as usual:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if(segue.identifier == "TaskListSegue"){
let detailViewController = segue.destinationViewController as ToDoTaskViewController
let (task, desc) = m_ToDoListManager.GetTask(TaskListView.indexPathForSelectedRow().row)
println("selected \(task) \(desc)")
detailViewController.taskName = task
detailViewController.taskDescription = desc
}
}
The way everything is implemented is correct.
But now when you run the application, the values of text controls are not set.
In fact, the values of the variables also are not set.
What must be happening here?
I have already investigated this problem and also came up with a solution (see my answer below). Please also see Martin R's answer below for a detailed explanation. I just wanted to share this with everyone. I am not sure if anyone has come across this issue.
Update:
Here's the actual code:https://github.com/Abbyjeet/Swift-ToDoList
Here is an explanation:
Your Swift class is (ultimately) a subclass of NSObject.
Therefore the properties are Objective-C properties with getter and setter method.
The name of the setter method for a property is built by capitalizing the first
letter of the property name, e.g. property "foo" has the setter method setFoo:
As a consequence, the setter method for both properties TaskName and taskName is called setTaskName:.
In an Objective-C file, you would get a compiler error
synthesized properties 'taskName' and 'TaskName' both claim setter 'setTaskName:' - use of this setter will cause unexpected behavior
but the Swift compiler does not notice the conflict.
A small demo of the problem:
class MyClass : NSObject {
var prop : String?
var Prop : String?
}
let mc = MyClass()
mc.prop = "foo"
mc.Prop = "bar"
println(mc.prop) // bar
println(mc.Prop) // nil
In your case
TaskName.text = ...
sets the "taskName" property, not the "TaskName". The properties have different type,
so that the behavior is undefined.
Note that the problem does only occur for "Objective-C compatible" properties. If you remove the
NSObject superclass in above example, the output is as expected.
Conclusion: You cannot have two Objective-C properties that differ only in the
case of the first letter. The Swift compiler should fail with an error here (as the
Objective-C compiler does).
The problem you were facing with was not connected to the swift language. Method prepareForSegue is called before loadView. That mean UITextField and UITextView are not initialized yet. That's why fields were not initialized.
You also asked: Why compiler doesn't show any error? That's because any selector performed on nil object doesn't throw an exception. So for example (sorry for obj-c):
UITextField *tf = nil;
[tf setText:#"NewText"];
Will not show any error.
As you said on your own answer to solve your problem you need to add additional fields to your destination controller (copy-paste):
var tAskName:String? //cUstomCamelCase
var tAskDescription:String? //cUstomCamelCase
Why is it happening?
I believe that internally Swift is using lowerCamelCase for text controls names which are not yet initialized and thus failing to set the values. But it is also strange that I didn't get any kind of error.
How did I solve it?
I know that the Swift is case-sensitive. So that was not the issue. So I just changed the case of one letter and named the variables as (tAskName, tAskDescription) and the values were set as expected.
#IBOutlet var TaskName:UITextField! //UpperCamelCase
#IBOutlet var TaskDescription:UITextView! //UpperCamelCase
var tAskName:String? //cUstomCamelCase
var tAskDescription:String? //cUstomCamelCase
So the conclusion is that if I have a control named TaskName, I cannot have a variable named as taskName