I need to accept function as a parameter after accepting function I want that function to be done on viewcontroller with self code below for better understanding
private func addSwipeControllerLeft(name: String, color: UIColor) -> UISwipeActionsConfiguration {
let action = UIContextualAction(
style: .normal,
title: name
) { [weak self] (action, view, completionHandler) in
self?.handleMarkAsFavourite() // I need to create function with accepts another function as a parametre
completionHandler(true)
}
}
I tried generics but it returns an error Value of type 'FeedViewController' has no member 'T'
private func addSwipeControllerLeft2<T>(name: String, color: UIColor, pFunction: T) -> UISwipeActionsConfiguration {
let action = UIContextualAction(
style: .normal,
title: name
) { [weak self] (action, view, completionHandler) in
self?.T
completionHandler(true)
}
}
any solutions ?
There is no need for a generic Type here. Add your desired function signature to addSwipeControllerLeft2s signature and call it inside your closure.
// declare the signature here
private func addSwipeControllerLeft2(name:String,color:UIColor, pFunction: #escaping ()->Void) -> UISwipeActionsConfiguration{
let action = UIContextualAction(style: .normal, title: name) { [weak self] (action, view, completionHandler) in
pFunction() // invoke it here
completionHandler(true)
}
}
you could call it this way:
private func TestFunc(){
}
func TestAddSwipe(){
// you can pass any function to pFunction: you want as long as it
// satisfies the signature ()->Void
addSwipeControllerLeft2(name: "", color: .red, pFunction: TestFunc)
}
Calling a passed function on self cannot be done in this way, but if you pass the ViewController function as a parameter, it will be called on self:
class ViewController: UIViewController {
private func addSwipeControllerLeft2(name: String, color: UIColor, function: #escaping () -> Void) -> UISwipeActionsConfiguration{
let action = UIContextualAction(style: .normal,
title: name) { (action, view, completionHandler) in
function()
completionHandler(true)
}
return UISwipeActionsConfiguration(actions: [action])
}
private func handleMarkAsFavourite() {
print("Called on", self)
}
}
Example:
addSwipeControllerLeft2(name: "Test", color: .red, function: self.handleMarkAsFavourite)
Related
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.
I am trying to implement Stripe Add Payment Method in SwiftUI. So a user can add a payment method or select from listed. After many days of searching I was able to implement a working PaymentOptionsView. However, when add new card is clicked it does not display the STPAddCardViewController to enter new payment methods
Here is the code that display the payment option
import Foundation
import SwiftUI
import Stripe
struct PaymentOptionsView: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, STPPaymentOptionsViewControllerDelegate {
var control: PaymentOptionsView
init(_ control: PaymentOptionsView) {
self.control = control
}
// Implement required delegate methods here:
func paymentOptionsViewControllerDidCancel(_ paymentOptionsViewController: STPPaymentOptionsViewController) {
}
func paymentOptionsViewControllerDidFinish(_ paymentOptionsViewController: STPPaymentOptionsViewController) {
}
func paymentOptionsViewController(_ paymentOptionsViewController: STPPaymentOptionsViewController, didFailToLoadWithError error: Error) {
}
}
func makeUIViewController(context: UIViewControllerRepresentableContext<PaymentOptionsView>) -> STPPaymentOptionsViewController {
let config = STPPaymentConfiguration()
// config.requiredBillingAddressFields = .none
config.appleMerchantIdentifier = "dummy-merchant-id"
return STPPaymentOptionsViewController(configuration: config, theme: STPTheme(), apiAdapter: STPCustomerContext(keyProvider: MyAPIClient()), delegate: context.coordinator)
}
func updateUIViewController(_ uiViewController: STPPaymentOptionsViewController, context: UIViewControllerRepresentableContext<PaymentOptionsView>) { }
}
and here is APIClient
class MyAPIClient: NSObject, STPCustomerEphemeralKeyProvider {
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
let customerId = "cus_LoK4MNElrbzeVg"
let url = "https://us-central1-test-mmmm.cloudfunctions.net/app/createEphemeralKey"
AF.request(url, method: .post, parameters: [
"api_version": "2020-08-27",
"customerId": customerId,
])
.validate(statusCode: 200..<300)
.responseJSON { responseJSON in
switch responseJSON.result {
case .success(let json):
completion(json as? [String: AnyObject], nil)
case .failure(let error):
print("Error creating customer Key (retrieving ephemeral key) with Alamofire. See: MyAPIClient - func createCustomerKey")
completion(nil, error)
}
}
}
}
What am I doing wrong and How do I implement the add new card method?
This code actually works fine. I was initially presenting PaymentOptionsView through .sheet(isPresented: so it does not allowing transition to a new view when the add card button is pressed. When the PaymentOptionsView is presented as
.background( // add a hidden `NavigationLink` in the background
NavigationLink(destination: PaymentOptionsView(), isActive: $showPaymentSheet) {EmptyView()}
.hidden()
)
everythings works. when the add new card button is pressed, the view transition to STPAddCardViewController perfectly.
I am looking to structure a SwiftUI component in a similar way as some of Apple's implementations (E.g. Button). I am looking to have a label associated to this component (which is just another View) or have the ability to construct using just a string (and default to using a Text view).
This is what I have:
struct ExampleComponent<Label> : View where Label : View {
let label: () -> Label
let action: () -> ()
init(_ title: String, action: #escaping () -> ()) {
self.init(label: {
Text(title)
}, action: action)
}
init(#ViewBuilder label: #escaping () -> Label, action: #escaping () -> ()) {
self.label = label
self.action = action
}
var body: some View {
label()
}
}
However, this cannot compile due to the error:
Cannot convert value of type 'Text' to closure result type 'Label'.
What's going wrong?
If you want this to be a default, then you need to make sure it only applies in cases where Label == Text. In other situations, you can't use it. I generally do this with a constrained extension:
extension ExampleComponent where Label == Text {
init(_ title: String, action: #escaping () -> ()) {
self.init(label: {
Text(title)
}, action: action)
}
}
But you can also add the constraint on the init directly:
init(_ title: String, action: #escaping () -> ()) where Label == Text {
self.init(label: {
Text(title)
}, action: action)
}
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
Need to wrap the extension for the view controller instantiating inside the dispatch main thread, but got that error, any ideas how to resolve it?
extension UIStoryboard {
convenience init(name: StoryboardName) {
self.init(name: name.rawValue, bundle: nil)
}
func instantiateVC<T: UIViewController>(identifier: String = T.identifier) -> T {
// swiftlint:disable force_cast
DispatchQueue.main.async {
let controller = self.instantiateViewController(withIdentifier: identifier) as! T
controller.removeBackButtonTitle()
return controller
}
// swiftlint:enable force_cast
}
func instantiateInitialVC() -> UIViewController {
return self.instantiateInitialViewController()!
}
}
instantiateVC should not return a value. You need to add a new argument to this function to pass controller:
func instantiateVC<T: UIViewController>(identifier: String = T.identifier, completion: #escaping (T) -> Void) {
// swiftlint:disable force_cast
DispatchQueue.main.async {
let controller = self.instantiateViewController(withIdentifier: identifier) as! T
controller.removeBackButtonTitle()
completion(controller)
}
// swiftlint:enable force_cast
}