How to use RACObserve in swift - objective-c

this new existing project is using Objective-C Code with RACObserve(Reactive cocoa ) to read api responses.
I want to convert obj-c to swift .
Current Obj-c Implementation is :
#interface ObjCTableViewCell : UITableViewCell
#property (nonatomic, strong) OfferPersonal *offer;
#end
.m class->
- (void)bindToModel {
[RACObserve(self, offer.lender.name) subscribeNext:^(id x) {
self.nameLabel.text = x;
}];
}
Controller ->
{
ObjCTableViewCell TableViewCell *myCell;
myCell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
[myCell setValue:self.offer forKey:#"offerPersonal"];
[myCell bindToModel];
}
Now how to use RACObserve in Swift. i tried to search it over other places.
(http://blog.scottlogic.com/2014/07/24/mvvm-reactivecocoa-swift.html)could not understand exactly .

Is the new project using ReactiveSwift with everything (model, cell, view controller) being rewritten in Swift? If not, you can use ReactiveObjcBridge to call the old ReactiveCocoa ObjC library from Swift.
If yes, you should use replace the name String property with a MutableProperty<String>, and you can bind to the MutableProperty - two options are:
a: the bind operator <~
nameLabel.reactive.text <~ offer.lender.name
or
b: by accessing its signal
offer.lender.name.signal.observeValues { (value: String) in
nameLabel.text = value
}
Also, depending on how many times the property is updated, you might want to limit the observation to only one value, like .signal.take(first: 1).observeValues { ... } or signal.take(until: reactive.prepareForReuse)

Related

Convert Swift 2 closure to Objective-C block

I'm trying to build an Objective-C block in Swift 2 in order to add it to an NSArray like so :
typealias CompletionBlock = () -> Void
let aBlock:CompletionBlock = {
print("Hello world!")
}
let nsArray = NSMutableArray()
nsArray.addObject(aBlock) // Error
I know it will work just fine with a Swift array, but I need an NSArray here for compatibility with existing Objective-C code. And if I use a swift array the compiler will refuse to cast it to an NSArray because it won't be a [AnyObject] (it will be a [Any]).
The problem here is that a swift closure is not an object contrary to Objective-C blocks which are objects behind the scene (they are instances of NSBlock which is a subclass of NSObject)
So my question is : How do a create an Objective-C block in swift ? I've tried using #convention (block) in the typealias but it doesn't work.
EDIT : As of Swift 3, this is completely unnecessary (and doesn't even work). Adding closures to Objective-C arrays works out of the box in Swift 3. The answer below is valid for Swift 2 only.
I know this is a duplicate but I will still post a refactored answer from swift-closure-as-anyobject and cast-closures-blocks in case anyone lands on this one first.
The solution is to use the unsafeBitCast function to convert the Swift closure to an Objective-C compatible object before adding it to an NSArray and back before using it in Swift.
// The `#convention(block)` is important here in order to get
// Objective-C like memory management
typealias CompletionBlock = #convention(block) () -> Void
let aBlock:CompletionBlock = {
print("Hello world!")
}
let nsArray = NSMutableArray()
let blockObject = unsafeBitCast(aBlock, AnyObject.self)
nsArray.addObject(blockObject)
let closureObject = nsArray[0]
let closure = unsafeBitCast(closureObject, CompletionBlock.self)
closure()

Use delegate method written in Objective-C in Swift code

I'd like to use a delegate method written in Objective-C in Swift. The method is included in the MGSwipeTableCell framework (MGSwipeTableCell.h).
Objective-C:
-(BOOL) swipeTableCell:(MGSwipeTableCell*) cell tappedButtonAtIndex:(NSInteger) index direction:(MGSwipeDirection)direction fromExpansion:(BOOL) fromExpansion;
I try to convert it into Swift to and use the method:
func swipeTableCell(cell:MGSwipeTableCell, index:Int, direction:MGSwipeDirection, fromExpansion:Bool) -> Bool {
return true
}
But I don't know why but the function isn't getting called. Did I something wrong? I just want to get the indexPath of the swiped cell with this function.
You should implement MGSwipeTableCellDelegate protocol in your table view controller first. So you can just write:
class TableViewController : UITableViewController, MGSwipeTableCellDelegate {
....
....
....
func swipeTableCell(cell:MGSwipeTableCell, index:Int, direction:MGSwipeDirection, fromExpansion:Bool) -> Bool {
return true
}
}
and then when creating cells in cellForRowAtIndexPath: method you should create it like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let reuseIdentifier = "cell"
var cell = self.table.dequeueReusableCellWithIdentifier(reuseIdentifier) as! MGSwipeTableCell!
if cell == nil {
cell = MGSwipeTableCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: reuseIdentifier)
}
cell.delegate = self
return cell
}
Then you'll be able to track when swipe method is called because you set the cell delegate property.
It appears that the library you're trying to use has not adopted Objective-C nullability annotations yet, as such, any return values or arguments which are objects will be translated into Swift as implicitly unwrapped optionals (with the exclamation mark).
So, the signature you're looking for is this:
func swipeTableCell(cell: MGSwipeTableCell!, tappedButtonAtIndex: Int, direction: MGSwipeDirection, fromExpansion: Bool) -> Bool
But with that said, you need to add the protocol conformance anyway. If you do that first then try to write this method out, it should autocomplete to exactly how Swift expects it to look.
And then just make sure you're actually setting the cell's delegate property to whatever object is implement this method.

Receiving NSDraggingDestination messages with a WKWebView

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

Objective-C bindings - Binding an enum to an NSPopupButton

I'm working on a project which would be ideally suit Cocoa bindings for the UI but I'm having an issue binding the value of an object property and can't find a suitable solution. The object is as follows:
typedef enum tagCSQuality {
kQualityBest = 0,
kQualityWorst = 1
} CSQuality;
#interface CSProfile : NSObject {
NSString *identifier;
NSString *name;
CSQuality quality;
}
In the XIB, I have an object controller whose content object is bound to a "currentSelection" property of the window controller which is an instance of the above object. I've then bound the name and identifier which all work as expected but I cannot see how I can bind the enums.
Ideally I would like an NSPopupButton to display "Best" and "Worst" and pick the correct enum value. I have updated the enum to have an explicit numeric value and I believe that I need a value transformer to convert the values but I'm stuck on exactly how this could be implemented.
Can anyone help me out or point me in the right direction?
Thanks,
J
You can use an NSValueTransformerfor this.
Since the enumeration values are integers only, they are encapsulated in an NSNumber object.
An valid transformer could look like the following.
+(Class)transformedValueClass {
return [NSString class];
}
-(id)transformedValue:(id)value {
CSQuality quality = [value intValue];
if (quality == kQualityBest)
return #"Best";
else if (quality == kQualityWorst)
return #"Worst";
return nil;
}
This can be bound to the Selected Value binding of the NSPopupButton.
If you want to create a bidirectional binding (i.e. be able to select something in the NSPopupButton you have to add the following code for the reverse transformation:
+(BOOL)allowsReverseTransformation {
return YES;
}
-(id)reverseTransformedValue:(id)value {
if ([#"Worst" isEqualToString:value])
return [NSNumber numberWithInt: kQualityWorst];
else if ([#"Best" isEqualToString:value])
return [NSNumber numberWithInt: kQualityBest];
return nil;
}
An enum is not an object. Cocoa bindings are a way to connect model objects to view objects.
If you are using Interface Builder, you can embed enum represented integer for each NSMenuItem items through property panel. Then select NSPopUpButton and specify binding 'selected tag' to the property with key path.
In this example, assume, IB's file owner is CSProfile. Prepare NSPopUpButton with two NSMenuItem items and tag them with 0(kQualityBest) and 1(kQualityWorst). Then navigate 'selected tag' of NSPopUpButton and check bind to 'File's owner'(CSProfile) with Model Key Path 'quality'.
#interface CSProfile : NSObject {
NSString *identifier;
NSString *name;
CSQuality quality;
}
#property (assign) CSQuality quality;

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)
}
}