"Prepare for segue" for WatchKit - ios

I am attempting to transfer a string from one view controller to another. The Problem that I having is that it is required to set the string 'as any object'.
The code I have below returns a nil in the console. Is there any way to send data across VC's as strings? Thanks in advance.
In MainVc-
func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
// You may want to set the context's identifier in Interface Builder and check it here to make sure you're returning data at the proper times
// Return data to be accessed in ResultsController
var currentValue = "Hellooo"
return currentValue as String as AnyObject
}
In receiving VC.
override func awake(withContext context: Any?) {
super.awake(withContext: context)
if let val: Any = context as? Any {
print(val)
} else {
print("An error occurred")
} // Configure interface objects here.
}
The output is simply nil.

First of all currentValue as String has no effect, currentValue is already a String.
Secondly, casting val to Any again makes no sense, since val is already of non-explicit type, Any. You should conditional cast it to String, since you are expecting a String variable.
For such a simple case, I wouldn't use 'contextForSegueWithIdentifier' at all, I would just simply use presentController(withName:context:) or pushController(withName:context:).
Using these you don't need to do any casting, you can simply do presentController(withName: "MyContoller", context: "Hello").

I came across with the same problem. The solution was as Larme suggested, used the appropriate function for swift 3 or 4, in this case, I assume you tried to use this code
override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
but the compiler suggests that doesn't have to be overridden because Method does not override any method from its superclass
The easy solution is to remove the override. The app compiles but the contextForSegue function is never dispatched.
The correct structure is as follow:
override func contextForSegue(withIdentifier segueIdentifier: String) -> Any?
{

Related

Crash when adopting NSSecureUnarchiveFromDataTransformer for a Transformable property

In iOS 12 Apple introduced NSSecureUnarchiveFromDataTransformerName for use on CoreData model entities' Transformable properties. I used to keep the Transformer Name field empty, which implicitly used NSKeyedUnarchiveFromDataTransformerName. This transformer is now deprecated, and keeping the field empty in the future will mean NSSecureUnarchiveFromDataTransformerName instead.
In iOS 13, if that field is empty, you now get a runtime warning telling you the aforementioned. I couldn't find any documentation on this anywhere, the only reference I got was a WWDC 2018 Core Data Best Practices talk which briefly mentioned what I just said.
Now I have a model with an entity which directly stores HTTPURLResponse objects in a Transformable property. It conforms to NSSecureCoding, and I checked in runtime that supportsSecureCoding is true.
Setting NSSecureUnarchiveFromDataTransformerName for the Transformer Name crashes with this message:
Object of class NSHTTPURLResponse is not among allowed top level class list (
NSArray,
NSDictionary,
NSSet,
NSString,
NSNumber,
NSDate,
NSData,
NSURL,
NSUUID,
NSNull
) with userInfo of (null)
So it sounds like Transformable properties can only be of these top level objects.
I tried subclassing the secure transformer and override the allowedTopLevelClasses property as suggested by the documentation:
#available(iOS 12.0, *)
public class NSSecureUnarchiveHTTPURLResponseFromDataTransformer: NSSecureUnarchiveFromDataTransformer {
override public class var allowedTopLevelClasses: [AnyClass] {
return [HTTPURLResponse.self]
}
}
Then I'd imagine I can create a custom transformer name, set it in the model and call setValueTransformer(_:forName:) for that name, but I couldn't find API to set the default NSKeyedUnarchiveFromDataTransformer for my custom name in case I'm on iOS 11.
Keep in mind, I'm using Xcode 11 Beta 5, but this doesn't seem to be related if I am to accept the meaning of the error I'm getting as stated.
Appreciate any thoughts.
I wrote a simple template class which makes it easy to create and register a transformer for any class that implements NSSecureCoding. It works fine for me in iOS 12 and 13, at least in my simple test using UIColor as the transformable attribute.
To use it (using UIColor as an example):
// Make UIColor adopt ValueTransforming
extension UIColor: ValueTransforming {
static var valueTransformerName: NSValueTransformerName {
.init("UIColorValueTransformer")
}
}
// Register the transformer somewhere early in app startup.
NSSecureCodingValueTransformer<UIColor>.registerTransformer()
The name of the transformer to use in the Core Data model is UIColorValueTransformer.
import Foundation
public protocol ValueTransforming: NSSecureCoding {
static var valueTransformerName: NSValueTransformerName { get }
}
public class NSSecureCodingValueTransformer<T: NSSecureCoding & NSObject>: ValueTransformer {
public override class func transformedValueClass() -> AnyClass { T.self }
public override class func allowsReverseTransformation() -> Bool { true }
public override func transformedValue(_ value: Any?) -> Any? {
guard let value = value as? T else { return nil }
return try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true)
}
public override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? NSData else { return nil }
let result = try? NSKeyedUnarchiver.unarchivedObject(
ofClass: T.self,
from: data as Data
)
return result
}
/// Registers the transformer by calling `ValueTransformer.setValueTransformer(_:forName:)`.
public static func registerTransformer() {
let transformer = NSSecureCodingValueTransformer<T>()
ValueTransformer.setValueTransformer(transformer, forName: T.valueTransformerName)
}
}
I tried to use NSSecureUnarchiveFromDataTransformer also (although I don't need secure coding, see below), but I did not have success. Thus I used a custom value transformer instead. My steps were:
I implemented my custom value transformer class:
#objc(MyTransformer)
class MyTransformer: ValueTransformer {
override class func setValueTransformer(_ transformer: ValueTransformer?, forName name: NSValueTransformerName) {
ValueTransformer.setValueTransformer(transformer, forName: name)
}
override func transformedValue(_ value: Any?) -> Any? {
guard let value = value else { return nil }
let data = serialize(value) // A custom function, e.g. using an NSKeyedArchiver
return data as NSData
}
override class func allowsReverseTransformation() -> Bool {
return true
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let value = value else { return nil }
guard let data = value as? Data else { return nil }
let set = deserialize(data) // A custom function, e.g. using an NSKeyedUnarchiver
return set as NSSet // Or as an NSArray, or whatever the app expects
}
}
extension NSValueTransformerName {
static let myTransformerName = NSValueTransformerName(rawValue: „MyTransformer")
}
The 1st line (#objc) is required, see this post! Otherwise coreData does not recognise the custom transformer!
Next, I implemented a computed property in the app delegate, according to this post:
private let transformer: Void = {
MyTransformer.setValueTransformer(MyTransformer(), forName: .myTransformerName)
}()
It is important to do this early, e.g. in the app delegate, so that coreData does recognise the transformer when it is initialised.
Eventually, I set in the attribute inspector of the transformable attribute in the xcdatamodeld file the Transformer value to MyTransformer.
Then the code run correctly without run time logs.
Please note: In my case, it was not necessary to do secure coding, but the code above can easily be modified to use secure coding instead. Just modify the functions serialize and deserialize accordingly.
EDIT (due to the comment of kas-kad below):
Sorry, my code was unfortunately not complete.
In the app delegate, I used the following computed property (see this link). This ensures that the value transformer is registered very early, even before init is run.
private let transformer : Void = {
let myTransformer = MyValueTransformer()
ValueTransformer.setValueTransformer(myTransformer, forName:NSValueTransformerName("MyValueTransformer"))
}()
And to override class func setValueTransformer does in my implementation obviously nothing. I copied it from somewhere (cannot remember). So one can surely omit it.
The extension of NSValueTransformerName does nothing more than to allow to use .myTransformerName as the transformer name.

Strange casting behaviour when using performSegue [duplicate]

FYI: Swift bug raised here: https://bugs.swift.org/browse/SR-3871
I'm having an odd problem where a cast isn't working, but the console shows it as the correct type.
I have a public protocol
public protocol MyProtocol { }
And I implement this in a module, with a public method which return an instance.
internal struct MyStruct: MyProtocol { }
public func make() -> MyProtocol { return MyStruct() }
Then, in my view controller, I trigger a segue with that object as the sender
let myStruct = make()
self.performSegue(withIdentifier: "Bob", sender: myStruct)
All good so far.
The problem is in my prepare(for:sender:) method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Bob" {
if let instance = sender as? MyProtocol {
print("Yay")
}
}
}
However, the cast of instance to MyProtocol always returns nil.
When I run po sender as! MyProtocol in the console, it gives me the error Could not cast value of type '_SwiftValue' (0x1107c4c70) to 'MyProtocol' (0x1107c51c8). However, po sender will output a valid Module.MyStruct instance.
Why doesn't this cast work?
(I've managed to solve it by boxing my protocol in a struct, but I'd like to know why it's not working as is, and if there is a better way to fix it)
This is pretty weird bug – it looks like it happens when an instance has been bridged to Obj-C by being boxed in a _SwiftValue and is statically typed as Any(?). That instance then cannot be cast to a given protocol that it conforms to.
According to Joe Groff in the comments of the bug report you filed:
This is an instance of the general "runtime dynamic casting doesn't bridge if necessary to bridge to a protocol" bug. Since sender is seen as _SwiftValue object type, and we're trying to get to a protocol it doesn't conform to, we give up without also trying the bridged type.
A more minimal example would be:
protocol P {}
struct S : P {}
let s = S()
let val : Any = s as AnyObject // bridge to Obj-C as a _SwiftValue.
print(val as? P) // nil
Weirdly enough, first casting to AnyObject and then casting to the protocol appears to work:
print(val as AnyObject as! P) // S()
So it appears that statically typing it as AnyObject makes Swift also check the bridged type for protocol conformance, allowing the cast to succeed. The reasoning for this, as explained in another comment by Joe Groff, is:
The runtime has had a number of bugs where it only attempts certain conversions to one level of depth, but not after performing other conversions (so AnyObject -> bridge -> Protocol might work, but Any -> AnyObject -> bridge -> Protocol doesn't). It ought to work, at any rate.
The problem is that the sender must pass through the Objective-C world, but Objective-C is unaware of this protocol / struct relationship, since both Swift protocols and Swift structs are invisible to it. Instead of a struct, use a class:
protocol MyProtocol {}
class MyClass: MyProtocol { }
func make() -> MyProtocol { return MyClass() }
Now everything works as you expect, because the sender can live and breathe coherently in the Objective-C world.
Still not fixed. My favorite and easiest workaround is by far chain casting:
if let instance = sender as AnyObject as? MyProtocol {
}
I came across this issue on macOS 10.14.
I have an _NSXPCDistantObject coming from Objc for which
guard let obj = remoteObj as? MyProtocol else { return }
returns
My solution was to define a c function in a separate header like this:
static inline id<MyProtocol> castObject(id object) {
return object
}
And then use like this:
guard let obj: MyProtocol = castObject(remoteObject) else { return }
Here's my solution. I didn't want to just make it into a class (re: this answer) because my protocol is being implemented by multiple libraries and they would all have to remember to do that.
I went for boxing my protocol into a struct.
public struct BoxedMyProtocol: MyProtocol {
private let boxed: MyProtocol
// Just forward methods in MyProtocol onto the boxed value
public func myProtocolMethod(someInput: String) -> String {
return self.boxed.myProtocolMethod(someInput)
}
}
Now, I just pass around instances of BoxedMyProtocol.
I know this issue was resolved with swift 5.3 but sometimes you have to support older versions of iOS.
You can cast to the protocol if you first cast it to AnyObject.
if let value = (sender as? AnyObject) as? MyProtocol {
print("Yay")
}

How to pass a Struct to a method that is expecting an AnyObject?

I am starting to get my head around Structs in Swift, and changed one of our app's data models to a Struct.
In a view controller we have, we use this method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//Some code
self.performSegueWithIdentifier("JobDetailSegue", sender: job)
}
job above is of type JobModel, which is a class that I just converted to a struct.
However, compilation is failing on the line with self.performSegueWithIdentifier("JobDetailSegue", sender: job) with error Cannot convert value of type 'JobModel?' to expected argument type 'AnyObject?'
So I take it that methods that accept an AnyObject? won't take a Struct as an argument? If so, what is the correct way to use Structs in this scenario (if at all)?
I think maybe you can still use your Struct, but add a variable to it that returns a dictionary format of everything that's in your struct, which inherits from NSObject as #dasdom was saying. So you could do something like this:
struct JobModel {
var someData: String!
var someMoreData: AnyObject!
//all of your other struct members, and then a last one which returns a dict
var jobsDict [String: AnyObject] {
return ["someKey": someData, "anotherKey": someMoreData]
}
}
Then, when you perform your segue, instead of passing the whole job object, you can just do:
self.performSegueWithIdentifier("identifier", sender: job.jobsDict)
Yes you can with Generics in Swift
Create a class Trasporter
class Transporter<T> {
var data:T!
}
Now Did following code in your didSelcectRowAtIndexPath
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let job = Job()
//TODO:- set job object properties hear
// or you can also set object from your tableview datasource array like let job = data[indexPath.row]
let transporter = Transporter<Job>()
transporter.data = job
self.performSegueWithIdentifier("JobDetailSegue", sender: transporter)
}
Now you can receive it in prepareForSegue like following way
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "JobDetailSegue" {
if let detailsVC = segue.destinationViewController as? YourDetailsViewController
{
if let transporter = sender as? Transporter<Job> {
//now you can able to access your struct using transporter object's data property
//set struct in details ViewController
detailsVC.job = transporter.data
}
}
}
}
You can use Swift structs when dealing with Cocoa or Cocoa-touch APIs. The reason is, they expect, most of the time reference objects (pointers to reference objects, to be precise). You could wrap the struct into a class. But when dealing with Cocoa-touch APIs I believe the class has to inherit from NSObject (but I'm not sure about that).
Or you could expose the date you want to pass in an instance of NSDictionary an let the struct create it.
Or you could add a class for each struct that mirrors the structs properties and use the class instances in the communication to UIKit APIs.
Maybe a post I wrote a while a go also helps you.
Supposed that your struct is
var myStruct = MyStruct()
you have the get the struct pointer first using withUnsafeMutablePointer
var ref = withUnsafeMutablePointer(&myStruct) {UnsafeMutablePointer<Void>($0)}
This is just as memo to understand how it works, in fact you just have to define your function with a parameter of type withUnsafeMutablePointer
func myFunc(arg:UnsafeMutablePointer<Void>) {
}
and then pass the reference to myFunc
myFunc(&myStruct)
That's it.
Thanks for the answers. As per these answers and some research I have done, it is not possible to use Structs at all in this scenario, one has to some convert the Struct into a Class object. How to do this is captured well in these answers.
For my scenario, I chose to simply use a singleton with a static var that view controllers can consult. This is option is not better than the ones suggested here but it is quicker and functional for the small app that we are dealing with.

Swift: Unable to set property value on mutable object paramter

I have the following code:
func enableDelaysContentTouchesIncudlingSubviews(enable: Bool) {
self.setDelaysContentTouches(enable, forObject: self)
for obj: AnyObject in self.subviews {
self.setDelaysContentTouches(enable, forObject: obj)
}
}
private func setDelaysContentTouches(var value: Bool, var forObject obj: AnyObject) {
if obj.respondsToSelector("setDelaysContentTouches:") {
obj.delaysContentTouches = value
}
}
On the second function, the line obj.delaysContentTouches = value raises the following error: Cannot assign to property: 'obj' is immutable
I don't understand the reason since obj is declared as a var parameter. Therefor it should be mutable in my understanding.
Could somebody please explain me the reason and also provide a workaround.
Thanks in advance!
My guess is it's an issue of AnyObject technically being a protocol
Assuming you're working in a view, maybe try something along these lines:
func enableDelaysContentTouchesIncudlingSubviews(enable: Bool) {
self.setDelaysContentTouches(enable, view: self)
self.subviews.forEach({setDelaysContentTouches(enable, view: $0)})
}
private func setDelaysContentTouches(enable: Bool, view: UIView) {
if let viewAsScrollview = view as? UIScrollView {
viewAsScrollview.delaysContentTouches = enable;
}
}
This is a much swiftier way of doing things, more clear, and doesn't use "respondsToSelector"
More info, and possibly a more direct answer about respondsToSelector in swift here

I'm passing data from UITableViewController to UIViewController through protocols and instead of int value I'm getting nil. Why?

I have two UITableViewController, the first one:
protocol FetchUserProfileData {
func getNumberOfRequests()
}
class ListEvents: UITableViewController{
var fetchInfo:FetchUserProfileData?
func getNumberOfRequests() -> Int{
return 12
}
and the UIViewController:
class UserProfileDetails:UIViewController, FetchUserProfileData {
var listEvents: UserListEvents?
func getNumberOfRequests(){
}
override func viewDidLoad(){
listEvents?.fetchInfo = self
print(listEvents?.getNumberOfRequests())
and this line: print(listEvents?.getNumberOfRequests()) gives me a nil value instead of 12... What's wrong here?
---- edit
Ok, now I see that listEvents is empty... So my question is how can I pass that data from ListEvents to UserProfileDetails?
In this code, listEvents is probably nil.
But, the way you use the protocol looks odd to me. I would expect:
getNumberOfRequests in the protocol to return Int
ListEvents should be implementing the protocol, not UserProfileDetails
The empty getNumberOfRequests() in UserProfileDetails should be deleted
You did not set listEvents. When you are using story boards then you should set the fetchInfo not earlier than in (overwriting) prepareForSegue. Google for examples, the web is full of them. When you segue programmatically then you can set the property not before you actually instanticated the new view controller. You are better of using listEvents!.fetchInfo = self because in that case you'll get an exception when listEvents is nil.
I made some change your code and this will pass data from ListEvents to UserProfileDetails.
protocol FetchUserProfileDelegate {
func getNumberOfRequests()->Int
}
class ListEvents: UITableViewController,FetchUserProfileDelegate{
var userProfile: UserProfileDetails?
override func viewDidLoad() {
userProfile = UserProfileDetails()
userProfile?.delegate = self
}
// MARK: FetchUserProfileDelegate
func getNumberOfRequests() -> Int{
return 12 // return as your target Int
}
}
class UserProfileDetails:UIViewController {
var delegate:FetchUserProfileDelegate?
override func viewDidLoad() {
if let _ = delegate{
let resultInt = delegate?.getNumberOfRequests() // get the Int form ListEvents
print(resultInt)
}
}
}
The idea of moving data from one controller to another is very common. Most of the time this is done using a segue. A controller can have a function called prepareForSegue. This function gets called before the transition happens. Inside the prepareForSegue function, the system gives you destination controller object. You take that object and set your data in it. When the transition happens, and your destination controller comes up, it already has the data you want to give to it.
Use Xcode and make a new project. Choose "Master-Detail Application". This will generate the code for you and it is a good example of how to pass data between controllers.

Resources