swift check an object's property existence - ios

I have the below UINib extension method, I was wondering if I can set a delegate for the unarchived view
public class func decodeView<T:UIView>(nibName name:String,className classType:T.Type,delegate:AnyObject) -> T {
let nib = UINib(nibName: name)
let topLevelObjects = nib.instantiateWithOwner(nil, options: nil)
let view = topLevelObjects[0] as T
view.setTranslatesAutoresizingMaskIntoConstraints(false)
//check if view.delegate exists then view.delegate = delegate
return view
}

If you're asking if Swift supports reflection, TL;DR: you need to subclass from NSObject. Else you get limited info.
In this question, Does Swift support reflection? you get a more detailed discussion about the possibilities you have.
Once you have this part cleared, an example of how to obtain a list of properties can be found in this SO Answer
Although a quick & dirty way could be just to try and access the property (using KVC) and catch the exception if it fails. Swift does NOT support Try/Catch/Finally constructs, but this nice hack allows you to write code like:
SwiftTryCatch.try({
// try something
}, catch: { (error) in
println("\(error.description)")
}, finally: {
// close resources
})

Related

How do I pass a configuration to KinWebBrowserViewController?

I'm trying to find the correct syntax everywhere and I'm just not understanding it.
How do I pass a configuration to KinWebBrowserViewController?
Here's the KinWebBrowserViewController code converted to Swift (I can also provide the original Objective-C)
/*
Initialize a basic KINWebBrowserViewController instance for push onto navigation stack
Ideal for use with UINavigationController pushViewController:animated: or initWithRootViewController:
Optionally specify KINWebBrowser options or WKWebConfiguration
*/
class func webBrowser() -> KINWebBrowserViewController? {
}
class func webBrowser(with configuration: WKWebViewConfiguration?) -> KINWebBrowserViewController? {
}
And here's where the browser is instantiated:
enum WebviewViewControllerFactory {
static func make(for url: String) -> WebBrowserViewController {
let webBrowser = WebBrowserViewController()
webBrowser.showsURLInNavigationBar = false;
webBrowser.showsPageTitleInNavigationBar = false;
webBrowser.barTintColor = UIColor.navBackground
webBrowser.loadURLString(url);
return webBrowser;
}
}
All I want to do is add the following configuration:
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "callbackHandler")
And add this configuration to the browser.
This seems like it should be easy, but I have been fighting with Swift syntax for hours trying to get what I feel should be fairly simple to work. I've tried initializers, I've tried additional subclassing, I've tried even discarding this and using WkWebView.
How do I set a single configuration on KinWebBrowser?

How can I correct editor.photoEditorDelegate = self?

I am still a beginner at Swift programming and I am trying to build a very basic iOS app. The app requires users to take a photo and edit them on a photo editor. I am having issues with using editor.photoEditorDelegate = self.
This is what the developer of the photo editor used in his example and it gave no problem but it's not working for me. It is giving an error of:
Cannot assign value of type 'PhotosViewController?' to type 'PhotoEditorDelegate?'
I have tried to fix it with:
editor.photoEditorDelegate = self as? PhotoEditorDelegate
but it just makes the app crash when the editor is called.
I declared the editor with:
let editor = PhotoEditorViewController(nibName:"PhotoEditorViewController",bundle: Bundle(for: PhotoEditorViewController.self))
The error is pretty self explanatory ! You need to add this delegate to your class name "PhotoEditorDelegate"
This is a sample code based on the information you have provided
class PhotosViewController: PhotoEditorDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let editor = PhotoEditorViewController(nibName:"PhotoEditorViewController",bundle: Bundle(for: PhotoEditorViewController.self))
editor.photoEditorDelegate = self
// Do any additional setup after loading the view.
}
}
You need to make your PhotosViewController your PhotoEditorDelegate:
class PhotosViewController: PhotoEditorDelegate {
...

Enabling two-way communication between two classes

So I have a custom view touchableView inside a ViewController.
touchableView informs ViewController of changes in its properties through a delegate protocol (ViewController being the delegate).
What is the best method to change properties of touchableView from ViewController (so the other way around)?
Is there a way to create a two-way delegate relationship between two classes?
Simply:
Assuming that you are already have touchableView instance in the ViewController, you should be able to set -or get-/call its properties and methods.
For instance, assume that you have the following method in touchableView class:
class func fromNib() -> TouchableView {
return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)![0] as! TouchableView
}
You should simply be able to:
let touchableView = TouchableView.fromNib()
thus:
// for example
touchableView.myProperty = "Hello"
I assumed that TouchableView has a string property called myProperty...
Remark:
For some reason, I suggest to implement some of property observers in TouchableView:
Property observers observe and respond to changes in a property’s
value. Property observers are called every time a property’s value is
set, even if the new value is the same as the property’s current
value.
They might be -somehow- useful to be implemented in your custom class. For clarity, let's consider -for instance- that if editing the value of myProperty should be effecting the background color of the view, it might be implemented as:
var myProperty: String = "Initial Value" {
willSet {
print("About to set value to: \(newValue)")
}
didSet {
backgroundColor = UIColor.red
}
}
Further Reading:
If you are looking for an advanced approach for two way bindings (Implementing MVVM), you might want to check this article.
I would also suggest to take a look at some frameworks that will be so helpful for such an approach, such as RxSwift, for a more simple framework, you might want to check ReactiveKit/Bond.

Create a copy of a UIView in Swift

Because objects are reference types, not value types, if you set a UIView equal to another UIView, the views are the same object. If you modify one you'll modifying the other as well.
I have an interesting situation where I would like to add a UIView as a subview in another view, then I make some modifications, and those modifications should not affect the original UIView. How can I make a copy of the UIView so I can ensure I add that copy as a subview instead of a reference to the original UIView?
Note that I can't recreate the view in the same way the original was created, I need some way to create a copy given any UIView object.
You can make an UIView extension. In example snippet below, function copyView returns an AnyObject so you could copy any subclass of an UIView, ie UIImageView. If you want to copy only UIView you can change the return type to UIView.
//MARK: - UIView Extensions
extension UIView
{
func copyView<T: UIView>() -> T {
return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self)) as! T
}
}
Example usage:
let sourceView = UIView()
let copiedView: UIView = sourceView.copyView()
You can't arbitrarily copy an object. Only objects that implement the NSCopying protocol can be copied.
However, there is a workaround: Since UIViews can be serialized to disk (e.g. to load from a XIB), you could use NSKeyedArchiver and NSKeyedUnarchiver to create a serialized NSData describing your view, then de-serialize that again to get an independent but identical object.
Update for iOS >= 12.0
Methods archivedData(withRootObject:) and unarchivedObject(with:) are deprecated as of iOS 12.0.
Here is an update to #Ivan Porcolab's answer using the newer API (since 11.0), also made more general to support other types.
extension NSObject {
func copyObject<T:NSObject>() throws -> T? {
let data = try NSKeyedArchiver.archivedData(withRootObject:self, requiringSecureCoding:false)
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? T
}
}
This answer shows how to do what #uliwitness suggested. That is, get an identical object by archiving it and then unarchiving it. (It is also basically what Ivan Porkolab did in his answer, but in a more readable format, I think.)
let myView = UIView()
// create an NSData object from myView
let archive = NSKeyedArchiver.archivedData(withRootObject: myView)
// create a clone by unarchiving the NSData
let myViewCopy = NSKeyedUnarchiver.unarchiveObject(with: archive) as! UIView
Notes
Unarchiving the data creates an object of type AnyObject. We used as! UIView to type cast it back to a UIView since we know that's what it is. If our view were a UITextView then we could type cast it as! UITextView.
The myViewCopy no longer has a parent view.
Some people mention some problems when working with UIImage. However, see this and this answer.
Updated to Swift 3.0
I think that you should link you UIView with a .nib and just create a new one.
Property will not be the same, but you keep appearance and methods.
An addition solution could be to just create a new UIView and then copy over any critical properties. This may not work in the OP's case, but it could very well work for other cases.
For example, with a UITextView, probably all you would need is the frame and attributed text:
let textViewCopy = UITextView(frame: textView.frame)
textViewCopy.attributedText = textView.attributedText
This is mostly just an update for iOS 12+
extension NSObject {
func copyObject<T:NSObject>() throws -> T? {
let data = try NSKeyedArchiver.archivedData(withRootObject:self, requiringSecureCoding:false)
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? T
}
Additionally you can use this pattern to copy View controller view
let vc = UIViewController()
let anotherVc = UIViewController()
vc.view = anotherVc.copyView()
You may need this for caching view controller or cloning.

Overriding CALayer's initWithLayer in Swift

I'm learning how to use CALayers and perform animations on their properties. To a beginner Apple's documentation is simply cryptic. I managed to find an example (called: CustomAnimatableProperty) in iOS's documentation which somewhat 'explains' how to do what I want:
// For CALayer subclasses, always support initWithLayer: by copying over custom properties.
-(id)initWithLayer:(id)layer {
if( ( self = [super initWithLayer:layer] ) ) {
if ([layer isKindOfClass:[BulbLayer class]]) {
self.brightness = ((BulbLayer*)layer).brightness;
}
}
return self;
}
Translating the method override to Swift however gives me a few errors:
The errors stem from my lacking understanding of what's going on here. I'm not sure what are we checking for in those nested if statements. Also I am a bit baffled by the usage of "=" in the main if(){} block. Shouldn't we be checking ("==") for equality?
But yeah any general help would mean the world. I've tried reviewing a few blog-posts / tutorials online, however non of them deals with this speciffic issue.
The self = [super init...] idiom is for Objective-C, not Swift. In Swift, init blocks aren't normal functions and don't return anything.
While we're at it, let's use the Swift idiom for downcasting. We also need to guarantee that size is initialized before we call super.init.
override init(layer: AnyObject!) {
if let layer = layer as? SegmentActiveLayer {
size = layer.size
} else {
size = 0
}
super.init(layer: layer)
}

Resources