Receiving NSDraggingDestination messages with a WKWebView - objective-c

I'm porting an OS X app which was using WebView to using WKWebView, the new "modern WebKit API" introduced in OS X Yosemite. My previous WebView subclass supported dropping files onto it by first calling [self registerForDraggedTypes:#[NSFilenamesPboardType]] and then simply implementing - (BOOL)performDragOperation:(id < NSDraggingInfo >)sender.
This doesn't work with the new WKWebView, as performDragOperation never gets called, nor do any of the NSDraggingDestination protocol methods that I tried.
I also tried making a parent NSView implement the protocol, and I'm still not getting the messages. Removing the WKWebView from the hierarchy makes the parent NSView receive those messages.
I also tried implementing the WKNavigationDelegate protocol to prevent the default drop behaviour of WKWebView to happen and this didn't change a thing either.
Edit: Upon further inspection (suggested by Scott Kyle / #appden on twitter), a private class WKView that implements the NSDraggingDestination protocol is a subview of the WKWebView. My code should likely try to get the dragging notifications before the WKView sees them and acts on them.

The only solution I found, thanks to Scott Kyle, was to replace the performDragOperation: method defined on WKView with my own implementation using object_getClass, class_getInstanceMethod and method_exchangeImplementations from the Objective-C runtime. Hopefully, in the future, the WKUIDelegate protocol will be extended to support custom hooks into the drag-and-drop protocol as implemented privately by WKWebView.
Here's an example showing how we exchange the implementation of the nested view's performDragOperation: with our own from our WKWebView subclass:
// Override the performDragOperation: method implemented on WKView so that we may get drop notification.
var originalMethod = class_getInstanceMethod(object_getClass(subviews[0]), "performDragOperation:")
var overridingMethod = class_getInstanceMethod(object_getClass(self), "performDragOperation:")
method_exchangeImplementations(originalMethod, overridingMethod)
And then the implementation where we delegate to a dropDelegate object.
override func performDragOperation(sender: NSDraggingInfo) -> Bool {
let myWebView = superview as MyWebView
if let dropDelegate = myWebView.dropDelegate {
return dropDelegate.webView(myWebView, performDragOperation: sender)
}
return false
}

I needed to retain WKWebView's handling of drags, but be able to introspect and modify pasteboards before it received them. Subclassing WKView is not really helpful since all of WKWebView's classes would still use the original, so I used the runtime swizzle proposed earlier + a class extension.
import WebKit
import Foundation
import ObjectiveC
extension WKView {
func shimmedPerformDragOperation(sender: NSDraggingInfo) -> Bool {
var pboard = sender.draggingPasteboard()
if let items = pboard.pasteboardItems {
for item in items {
if let types = item.types? {
for type in types {
if let value = item.stringForType(type.description) {
NSLog("DnD type(\(type)): \(value)")
}
}
}
}
}
return self.shimmedPerformDragOperation(sender) //return pre-swizzled method
}
}
var webview = WKWebView(frame: CGRectZero, configuration: WkWebViewConfiguration())
var wkview = (webview.subviews.first as WKView) // 1 per frame?
var origDnD = class_getInstanceMethod(WKView.self, "performDragOperation:")
var newDnD = class_getInstanceMethod(WKView.self, "shimmedPerformDragOperation:")
method_exchangeImplementations(origDnD, newDnD)
You do need to add a small snippet to your bridging header to extend WKView:
#import WebKit;
#interface WKView : NSView <NSTextInputClient> {
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)draggingInfo;
#end

Related

self class alloc for controllers equivalent in Swift

I had a class UIBaseClassViewController with convenient functions in objective c.Now i'm switching to swift and i'm trying to convert it's code into swift.the function giving me problem is
+(UIBaseClassViewController*)getController
{
return [[[self class] alloc] initWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
}
i was able to convert it but it's not working fine
static func getController() -> Self
{
print("sam controller class = \(String(describing:self))")
print("SAM controller = \(self.init(nibName: String(describing:self), bundle:Bundle.main))")
return self.init(nibName: String(describing:self), bundle:Bundle.main)
}
Output:
sam controller class = UILoginViewController
SAM controller = <Swift_And_Node.UIBaseClassViewController: 0x7f8a4ee13830>
created object is of type UIBaseClassViewController.it loads the nib fine but as object is of UIBaseClassViewController app crashes because it was not able to find functions in UIBaseClassViewController which are in UILoginViewController.
How can i make it create object of child class instead of parent.UILoginViewController in this case
for better Understanding showing adding code:
UIBaseClassViewController:
class UIBaseClassViewController: UIViewController {
static func getController() -> Self
{
print("sam controller class = \(String(describing:self))")
print("SAM controller = \(self.init(nibName: String(describing:self), bundle:Bundle.main))")
var object = self
return self.init(nibName: String(describing:self), bundle:Bundle.main)
}
}
UILoginViewController:
class UILoginViewController: UIBaseClassViewController {}
3rd controller who need UILoginViewController:
UILoginViewController.getController()
You either have to call this static function on desired view controller class or not making it static at all. Please see the example below to see how it works in Swift.
class ParentView: UIView {
static func printSelf() {
print(String(describing: self))
}
}
class ChildView: ParentView {}
ParentView.printSelf() // Prints ParentView
ChildView.printSelf() // Prints ChildView
Turns out we don't need to mention nib and bundle for controller object...I moved from objective c and these things are necessary there.
with
[[UILoginViewController alloc] init]
app will show black screen.
In swift we can just use UILoginViewController() and it will automatically associate nib with the controller object.
so to answer my question i just used
self.init()
instead of
self.init(nibName: String(describing:self), bundle:Bundle.main)

_cmd in Swift for selector use

I'm trying to write the following ObjC code in Swift 3:
- (void)scrollViewScroll:(UIScrollView*)scrollView {
// some code
if ([_userDelegate respondsToSelector:_cmd]) {
[_userDelegate scrollViewDidEndDecelerating:scrollView];
}
}
But do not know what to replace _cmd with. I'm trying function, but it doesn't work:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// some code
if (userDelegate?.responds(to: #function))! {
userDelegate?.scrollViewDidScroll!(scrollView)
}
}
using #selector(scrollViewDidScroll(_:)) works, but is there a way to keep it generic?
Edit: Possible duplicate answer is about getting function name which isn't what I'm asking above
Swift doesn't have selectors.
Objective-C sends messages to objects while Swift calls functions. So checking if object can respond to selector is part of Objective-C and NSObject.
Swift protocol functions are required by default. Swift compiler doesn't let you skip those function implementations. But you can make them optional, and you have to check, if these functions implemented before calling.
In this case, just call function with question mark at the end, like this
if let returnValue = userDelegate?.theOptionalFunction?(arguments) {
// you got value
} else {
// delegate returned nil or delegate function isn't implemented
}
Source: The Swift Programming Language
An optional protocol requirement can be called with optional chaining, to account for the possibility that the requirement was not implemented by a type that conforms to the protocol. You check for an implementation of an optional method by writing a question mark after the name of the method when it is called, such as someOptionalMethod?(someArgument).

What is the swift equivalent to setting properties on `id`?

I wonder what's the Swift equivalent in calling a method on id in which the availability of the method is determined at runtime. Specifically I'm looking to do this pattern in Swift:
-(IBAction) handleEvent:(id) sender {
BOOL didDisable = NO;
if([sender respondsToSelector:#selector(setEnabled:)]) {
[sender setEnabled:NO];
didDisable = YES;
}
[self doSomethingAsyncWithCompletionHandler:^{
if(didDisable) {
[sender setEnabled:YES];
}
}];
}
The biggest problem is that setEnabled: is imported in Swift as a property (e.g. UIBarItem) and none of the following constructs compile
func handleEvent(sender: AnyObject) {
// Error: AnyObject does not have a member named "enabled"
sender.enabled? = false
// Error: (BooleanLiteralCompatible) -> _ is not identical to Bool
sender.setEnabled?(false)
}
You can in fact do it exactly the same way you were doing it before: by calling respondsToSelector:. Indeed, that is exactly what your proposed expression does:
sender.setEnabled?(false)
That expression is actually a shorthand - it calls respondsToSelector: first, and then calls setEnabled: only if the respondsToSelector: test passes. Unfortunately, as you say, you can't get that code to compile. That, however, is merely a quirk of Swift's known repertory of available methods. The fact is that, although it is a little tricky to get it to compile, it can be done - and once you get it to compile, it behaves just as you would expect.
However, I'm not going to explain how to make it compile, because I don't want to encourage this kind of trickery. This sort of dynamic messaging is discouraged in Swift. In general, dynamic messaging tricks such as key-value coding, introspection, and so forth are not needed in Swift and are not consonant with Swift's strong typing approach. It would be better to do things the Swift way, by casting optionally to something that you have reason to believe this thing might be and that has an enabled property. For example:
#IBAction func doButton(sender: AnyObject) {
switch sender {
case let c as UIControl: c.enabled = false
case let b as UIBarItem: b.enabled = false
default:break
}
}
Or:
#IBAction func doButton(sender: AnyObject) {
(sender as? UIControl)?.enabled = false
(sender as? UIBarItem)?.enabled = false
}
In Swift 2.0 beta 4, your prayers are answered; this code becomes legal:
#IBAction
func handleEvent(sender: AnyObject) {
if sender.respondsToSelector("setHidden:") {
sender.performSelector("setHidden:", withObject: true)
}
}
If you want to avoid using the respondsToSelector: method you could define a protocol instead. Then extend the classes you want to use that is already in conformance with this protocol's definition (enabled) and define the function with a generic variable conforming to your protocol.
protocol Enablable{
var enabled:Bool { get set }
}
extension UIButton : Enablable {}
extension UIBarButtonItem : Enablable {}
//....
func handleEvent<T:Enablable>(var sender: T) {
sender.enabled = false
}
If you need to use it with an IBAction method a little bit of a work around is required since you cannot use generics directly on them.
#IBAction func handleEventPressed(sender:AnyObject){
handleEvent(sender);
}
We also need a matching generic function without Enablable conformance so that we can call handleEvent without knowing wether or not sender is Enablable. Luckily the compiler is smart enough to figure out which of the two generic functions to use.
func handleEvent<T>(var sender: T) {
//Do Nothing case if T does not conform to Enablable
}
As a workaround/alternative, you can use Key-Value Coding:
#IBAction func handler(sender: AnyObject) {
if sender.respondsToSelector("setEnabled:") {
sender.setValue(false, forKey:"enabled")
}
}
This works with both Swift 1.2 (Xcode 6.4) and Swift 2.0 (Xcode 7 beta).

How to check if a UIViewController is of a particular sub-class in objective c?

I want to be able to check the type of a UIViewController to see if it is of a certain type like this
c code
if (typeof(instance1) == customUIViewController)
{
customUIViewController test = (customViewController)instance1;
// do more stuff
}
The isKindOfClass: method indicates whether an object is an instance of given class or an instance of a subclass of that class.
if ([instance1 isKindOfClass:[CustomUIViewController class]]) {
// code
}
If you want to check whether an object is an instance of a given class (but not an instance of a subclass of that class), use isMemberOfClass: instead.
var someVC: UIViewController
if someVC is MyCustomVC {
//code
}
Swift version:
var someVC: UIViewController
if someVC.isKindOfClass(MyCustomVC) {
//code
}
Try:
[vc isKindOfClass:[CustomViewController class]];
I just wanted to add in addition to this answer that if you're wanting to see if a view controller is of a certain type in a switch statement (in Swift) you can do it like this:
var someVC: UIViewController?
switch someVC {
case is ViewController01: break
case is ViewController02: break
case is ViewController03: break
default: break
}
Swift 3.0
in latest, we have to add a self along with the class name
or it will throw an error "Expected member name or constructor call after type name"
the below code u can use for Swift 3 and above
for viewController in viewControllers {
if viewController.isKind(of: OurViewController.self){
print("yes it is OurViewController")
self.navigationController?.popToViewController(viewController, animated: true)
}
}

Override UILabel's setText method?

I'd like to override UILabel's setText method but I'm not sure that:
A) it's possible, and
B) if maybe there's a better way to achieve what I'm trying to accomplish.
I have a subclass of UIView that has a UILabel property as one of its sub-views. I'd like to know when the UILabel's "text" property changes so I can adjust the size of the rounded rect behind it. If I owned UILabel I'd just override setText...but UILabel is Apple's and its implementation is hidden.
So, how should I be going about this?
Subclasses of UILabel can override the setText method quite easily. I'm not really sure why this hasn't yet been included as a legitimate answer on this 4 year old question.
- (void) setText:(NSString *)text
{
[super setText:text];
[self sizeToFit];
}
You can use Key-Value Observing to track changes to the UILabel.text property. The approach involves three steps:
1) Registering to observe the property, when you load the view
[label addObserver:inspector
forKeyPath:#"text"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
2) Receiving a notification about any changes:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqual:#"text"]) {
// put your logic here
}
// be sure to call the super implementation
// if the superclass implements it
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
3) De-registering the observation whenever you aren't interested any more:
[label removeObserver:inspector forKeyPath:#"text"];
Matt answer is good for Objective-C, but doesn't work in Swift (normal, it didn't existed when he answered), the accepted answer from notnoop does work in swift, even though it is more complicated, just to give another idea in swift you can use the didSet:
class MyLabel: UILabel {
override var text: String? {
didSet {
if let text = text {
println("the new text is: \(text)")
} else {
println("the text has been set to nil")
}
}
}
Based on Drix answer, I think this is a more correct approach (using set instead of didSet):
class UnreadCountLabel: UILabel {
override var text: String? {
set {
if let newValue = newValue where !newValue.isEmpty {
super.text = " \(newValue) "
self.hidden = false
} else {
super.text = newValue
self.hidden = true
}
}
get {
return super.text
}
}
}
Are you just using a rounded rectangle as the background for the Label? If that is the case, you can look into using UIIMage stretchableImageWithLeftCapWidth:topCapHeight. This will take an image you've created that has a left and top repeating section with a width you specify and automatically stretch it to your width.
If not, Key-Value observing is the way to go. Just to cover another option--this is like "playing with fire," as Apple programmer Evan Doll said in one of his Stanford lectures--you can use method swizzling to exchange one method implementation for another.
void method_exchangeImplementations(Method m1, Method m2);
In this case, you want to tweak the implementation of setText, but you also want to call the original setText in UILabel. So you could exchange setText with setTextAndUpdateSize, and inside setTextAndUpdateSize do what setText does originally plus add on a little more. If you are confused or think this is a bad idea, it probably is. You can get a Method object to pass into method_exchangeImplementations by calling class_getInstanceMethod([NSSTring class], #selector (methodName).
Once your method swizzle has been called once, inside your new method you can then call the old implementation of setText from within the new one by using, yes, setTextAndUpdateSize. It's confusing and not recommended, but it works. A good example can be found in the developer sample code.
I pulled of Method Swizzling in Swift 2.0. Changing the font of the entire application by swapping the implementation of setText method of the label.
Copy the code in app delegate and use the customSetText to make application level changes
// MARK: - Method Swizzling
extension UILabel {
public override class func initialize() {
struct Static {
static var token: dispatch_once_t = 0
}
// make sure this isn't a subclass
if self !== UILabel.self {
return
}
dispatch_once(&Static.token) {
let originalSelector = Selector("setText:")
let swizzledSelector = Selector("customSetText:")
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
}
// MARK: - Custom set text method for UI Label
func customSetText(text: String) {
self.customSetText(text)
//set custom font to all the labels maintaining the size UILabel
self.font = UIFont(name: "Lato-LightItalic", size: self.font.pointSize)
}
}