Handle escaping closure in non escaping function - swift

In the following code I get the error.
Passing non escaping parameter 'completion' to function expecting an
#escaping closure.
protocol DetailViewControllerDelegate: AnyObject {
func detailViewController(_ controller: DetailViewController, doneButtonPressed button: UIBarButtonItem)
}
final class DetailViewControllerDelegateHandler: DetailViewControllerDelegate {
typealias DismissComplete = () -> Void
typealias Action = (DismissComplete) -> Void
var dissmissScene: Action?
private let model = Model()
func detailViewController(_ controller: DetailViewController, doneButtonPressed button: UIBarButtonItem) {
dissmissScene? { [unowned self] in
let value = controller.value
self.model.update(with: value)
}
}
}
final class HomeViewController: UIViewController {
private let delegateHandler = DetailViewControllerDelegateHandler()
override func viewDidLoad() {
super.viewDidLoad()
delegateHandler.dissmissScene = { completion in
// error here
self.dismiss(animated: true, completion: completion)
}
}
func buttonAction(_ sender: UIButton) {
let controller = DetailViewController.instantiateFromStoryboard()
controller.delegate = delegateHandler
present(controller, sender: self)
}
}
What can I do about this?

The completion closure is not escaping. Because dismissScene is a function that accepts a non-escaping closure. Notice that the type of dismissScene is Action, which is (DismissComplete) -> Void, which in turn is (() -> Void) -> Void. The inner () -> Void is not marked #escaping.
You just have to mark it as so:
typealias Action = (#escaping DismissComplete) -> Void

Related

NSItemProvider[URL] - how to COPY with drag&Drop instead of MOVE?

I have implemented function that returns NSItemProvider
func dragOutsideWnd(url: URL?) -> NSItemProvider {
if let url = url {
TheApp.appDelegate.hideMainWnd()
let provider = NSItemProvider(item: url as NSSecureCoding?, typeIdentifier: UTType.fileURL.identifier as String)
provider.suggestedName = url.lastPathComponent
//provider.copy()// This doesn't work :)
//DispatchQueue.main.async {
// TheApp.appDelegate.hideMainWnd()
//}
return provider
}
return NSItemProvider()
}
and I have use it this way:
.onDrag {
return dragOutsideWnd(url: itm.url)
}
This drag&drop action performs file MOVE action to any place of FINDER/HDD.
But how to perform COPY action?
Remember Drag&Drop is actually implemented with NSPasteboard.
I have written an example for you:
GitHub
Now the key to your questions:
To control dragging behavior(your window is the source):
Draggable objects conform to the NSDraggingSource protocol, so check the first method of the protocol:
#MainActor func draggingSession(
_ session: NSDraggingSession,
sourceOperationMaskFor context: NSDraggingContext
) -> NSDragOperation
As the method docsuggests, return different NSDragOperation in this delegation method. That includes: "Copy","Move", "Link", etc.
To control dropping behavior(your window is the destination):
NSView that accepts drop conforms to the NSDraggingDestination protocol, so you need to override the draggingEntered(_:) method by adding this code inside the DestinationView class implementation:
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation
{
var allow = true
//.copy .move, see more options in NSDragOperation, up to you.
return allow ? .copy : NSDragOperation()
}
More info form Apple's Documentation
For swiftUI, a simple show case SwiftUI Showcase
Further Reading: RayWenderlich.com has a detailed tutorial Drag and Drop Tutorial for macOS tutorial for you(needs a little swift upgrade).
Thanks a lot to answer of kakaiikaka!
The following solution works in swiftUI:
import Foundation
import SwiftUI
extension View {
func asDragable(url: URL, tapAction: #escaping () -> () , dTapAction: #escaping () -> ()) -> some View {
self.background {
DragDropView(url: url, tapAction: tapAction, dTapAction: dTapAction)
}
}
}
struct DragDropView: NSViewRepresentable {
let url: URL
let tapAction: () -> ()
let dTapAction: () -> ()
func makeNSView(context: Context) -> NSView {
return DragDropNSView(url: url, tapAction: tapAction, dTapAction: dTapAction)
}
func updateNSView(_ nsView: NSView, context: Context) { }
}
class DragDropNSView: NSView, NSDraggingSource {
let url: URL
let tapAction: () -> ()
let dTapAction: () -> ()
let imgMove: NSImage = NSImage(named: "arrow.down.doc.fill_cust")!
init(url: URL, tapAction: #escaping () -> (), dTapAction: #escaping () -> ()) {
self.url = url
self.tapAction = tapAction
self.dTapAction = dTapAction
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
return mustBeMoveAction ? .move : .copy
}
}
extension DragDropNSView: NSPasteboardItemDataProvider {
func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: NSPasteboard.PasteboardType) {
// If the desired data type is fileURL, you load an file inside the bundle.
if let pasteboard = pasteboard, type == NSPasteboard.PasteboardType.fileURL {
pasteboard.setData(url.dataRepresentation, forType:type)
}
}
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
tapAction()
if event.clickCount == 2 {
dTapAction()
}
}
override func mouseDragged(with event: NSEvent) {
//1. Creates an NSPasteboardItem and sets this class as its data provider. A NSPasteboardItem is the box that carries the info about the item being dragged. The NSPasteboardItemDataProvider provides data upon request. In this case a file url
let pasteboardItem = NSPasteboardItem()
pasteboardItem.setDataProvider(self, forTypes: [NSPasteboard.PasteboardType.fileURL])
var rect = imgMove.alignmentRect
rect.size = NSSize(width: imgMove.size.width/2, height: imgMove.size.height/2)
//2. Creates a NSDraggingItem and assigns the pasteboard item to it
let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
draggingItem.setDraggingFrame(rect, contents: imgMove) // `contents` is the preview image when dragging happens.
//3. Starts the dragging session. Here you trigger the dragging image to start following your mouse until you drop it.
beginDraggingSession(with: [draggingItem], event: event, source: self)
}
}
////////////////////////////////////////
///HELPERS
///////////////////////////////////////
extension DragDropNSView {
var dragGoingOutsideWindow: Bool {
guard let currEvent = NSApplication.shared.currentEvent else { return false }
if let rect = self.window?.contentView?.visibleRect,
rect.contains(currEvent.locationInWindow)
{
return false
}
return true
}
var mustBeMoveAction: Bool {
guard let currEvent = NSApplication.shared.currentEvent else { return false }
if currEvent.modifierFlags.check(equals: [.command]) {
return true
}
return false
}
}
extension NSEvent.ModifierFlags {
func check(equals: [NSEvent.ModifierFlags] ) -> Bool {
var notEquals: [NSEvent.ModifierFlags] = [.shift, .command, .control, .option]
equals.forEach{ val in notEquals.removeFirst(where: { $0 == val }) }
var result = true
equals.forEach{ val in
if result {
result = self.contains(val)
}
}
notEquals.forEach{ val in
if result {
result = !self.contains(val)
}
}
return result
}
}
usage:
FileIcon()
.asDragable( url: recent.url, tapAction: {}, dTapAction: {})
this element will be draggable and perform MOVE in case .command key pressed.
And will perform COPY in another case
Also it performs drag action only outside widndow. But it's easy to change.

Passing closure in userInfo to NotificationCenter, getting cryptic messages from compiler at runtime

I'm trying to pass a closure in userInfo through NotificationCenter. The closures works as expected but I'm getting weird messages that I do not understand in runtime. What is the cause of this message?
The message is:
0x000000010292b350 [ProjectName]`partial apply forwarder for reabstraction thunk helper from #escaping #callee_guaranteed () -> (#out ()) to #escaping #callee_guaranteed () -> () at <compiler-generated>
This is how I post the notification.
let closure: (() -> Void) = {
print("TEST")
}
NotificationCenter.default.post(name: .test,
object: nil,
userInfo: ["closure" : closure ])
This is how I consume the notificaiton:
#objc private func test(_ notification: Notification) {
let closure = notification.userInfo?["closure"] as? (() -> Void)
closure?()
}
I'm using Swift 5.
Unfortunately I can't reproduce the error, but I would suggest wrapping the closure in a wrapper class. Things that need to round-trip into and out of Cocoa do better when they are NSObjects:
class Wrapper : NSObject {
let closure : () -> Void
init(closure: #escaping () -> Void) {
self.closure = closure
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let closure: (() -> Void) = {
print("TEST")
}
let wrapper = Wrapper(closure: closure)
let test = Notification.Name("Test")
NotificationCenter.default.addObserver(self,
selector: #selector(testing), name: test, object: nil)
NotificationCenter.default.post(name: test,
object: nil, userInfo: ["wrapper" : wrapper ])
}
#objc private func testing(_ notification: Notification) {
let wrapper = notification.userInfo?["wrapper"] as? Wrapper
wrapper?.closure()
}
}

How to chain functions with a completion handler in Swift?

I am chaining some functions together and I can't figure out how to call a completion handler with a return value once all the functions are done running.
func getAirQuality(completion: (aqi: Int?) -> Void) {
callAPI()
}
private func callAPI() {
// ... get data
self.parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
let aqi = aqi
// Send aqi up to completion handler in getAirQuality
}
So that when everything is said and done I can just do something like this:
getAirQuality(completion: { aqi -> Void in {
// Do something with aqi
})
My first assumption is that your first 3 functions are part of a class. If so, one approach is to save the completion handler as an instance variable.
class AirQualityProvider {
var aBlock: ((Int?) -> Void)?
func getAirQuality(completion: #escaping (Int?) -> Void) {
aBlock = completion
callAPI()
}
private func callAPI() {
let data = Data()
parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
let aqi = 1
if let completion = aBlock {
completion(aqi)
}
}
}
Here's an example of a caller as written in a playground.
let aqp = AirQualityProvider()
aqp.getAirQuality { (value) in
if let value = value {
print("Value = \(value)")
}
}

Swift: How to use instanceType in callback closure

I want to pass self as instancetype to the callback closure of this function:
extension UIView {
public func onTap(_ handler: #escaping (_ gesture: UITapGestureRecognizer, _ view: Self) -> Void) -> UITapGestureRecognizer {
...
}
}
let view = UIView.init()
view.onTap { tap, v in
...
}
But I got an error:
Self' is only available in a protocol or as the result of a method in a class; did you mean 'UIView'?
How can I do this?
that is just the perfect scenario (by book) when you can use protocols and extensions in Swift quite efficiently:
protocol Tappable { }
extension Tappable { // or alternatively: extension Tappable where Self: UIView {
func onTap(_ handler: #escaping (UITapGestureRecognizer, Self) -> Void) -> UITapGestureRecognizer {
return UITapGestureRecognizer() // as default to make this snippet sane
}
}
extension UIView: Tappable { }
then for e.g.:
let button = UIButton.init()
button.onTap { tap, v in
// v is UIButton...
}
while for e.g.:
let label = UILabel.init()
label.onTap { tap, v in
// v is UILabel...
}
etc...
NOTE: you can read more about Extensions or the Protocols in the Swift Programming Language Book from Apple.

How to use #autoclosure parameter in async block in Swift?

I would like to call an #autoclosure parameter inside dispatch_async block.
func myFunc(#autoclosure condition: () -> Bool) {
dispatch_async(dispatch_get_main_queue()) {
if condition() {
println("Condition is true")
}
}
}
I get the following error.
Closure use of #noescape parameter may allow it to escape.
Is it possible to call #autoclosure parameter asynchronously?
Tested in Xcode 6.4 (6E23).
Yes, so long as you declare them #autoclosure(escaping):
Declarations with the autoclosure attribute imply noescape as well, except when passed the optional attribute escaping.
So this should do it:
func myFunc(#autoclosure(escaping) condition: () -> Bool) {
dispatch_async(dispatch_get_main_queue()) {
if condition() {
println("Condition is true")
}
}
}
Updating the answer from Airspeed Velocity, you can pass weak self directly into escaping autoclosure.
var boolValue: Bool = true
func myFunc(condition: #autoclosure #escaping () -> Bool?) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if let condition = condition() {
print("Condition is \(condition ? "true" : "false")")
}
}
}
func main() {
weak var weakSelf = self
myFunc(condition: weakSelf?.boolValue)
}