SwiftUI 4.0 how to know if ShareLink has been shared successfully - swift

I am now working with the new SwiftUI 4.0 and using the new ShareLink feature . How can I know if a link or text has been shared successfully, any suggestions would be great ? I have a simple ShareLink like this
ShareLink(item: InviteMessage) {
Text("Test Share")
.foregroundColor(.black)
}
before in SwiftUI 3.0 I would use this code
.background(SharingViewController(isPresenting: $InviteOthers) {
let message = "Share Me"
let av = UIActivityViewController(activityItems: [message], applicationActivities: nil)
av.completionWithItemsHandler = { (activity, success, items, error) in
if success {
// shared successfully update count
}
}
return av
})

It doesn't seem possible to know if the content has been shared successfully, but you can at least detect if the link has been clicked with the simultaneousGesture modifier (onTapGesture doesn't work on ShareLink):
ShareLink(){}
.simultaneousGesture(TapGesture().onEnded() {
print("clicked")
})

Im not sure its posible, but you definitely can add tokens to the url, and track when people open it. But as far as if it actually sent, i can’t help.

Related

Unable to save images to Egnyte/Google Drive/Dropbox - Swift/Xcode

UPDATE
I tried the following code solution and it allows for me to save to Google Drive now, but Egnyte and Dropbox are still greyed out.
func exportPhotosToFileLocation() {
var fileURLArray = [URL]()
for data in reviewDataController.tableViewReviewData {
guard let imageData = data.image.jpegData(compressionQuality: 1.00) else {
print("ERROR: Unable to print convert image to jpegData in exportPhotosToFileLocation!")
return
}
let fileManager = FileManager.default
do {
let fileURL = fileManager.temporaryDirectory.appendingPathComponent("\(data.imageTitle)").appendingPathExtension("jpeg")
try imageData.write(to: fileURL)
fileURLArray.append(fileURL)
print("Successfully created file from jpegData in exportPhotosToFileLocation!")
} catch {
print("ERROR: Unable to create file from jpegData in exportPhotosToFileLocation!")
return
}
}
if #available(iOS 14, *) {
let controller = UIDocumentPickerViewController(forExporting: fileURLArray)
present(controller, animated: true)
}
else {
let controller = UIDocumentPickerViewController(urls: fileURLArray, in: .exportToService)
present(controller, animated: true)
}
}
Here is the developer documents for Egnyte. Unfortunately, none of it makes sense to me as a beginner.
Egnyte Developer Documentation
----------------------------------------------------------------------------------------------
ORIGINAL POST
In my app, I'm trying to allow the user to select a save location (so choose a folder). Whenever I use this code, Egnyte/Google Drive/Dropbox are all "greyed" out and inaccessible.
let supportedTypes : [UTType] = [UTType.folder]
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes)
documentPickerController.delegate = self
self.present(documentPickerController, animated: true, completion: nil)
If I change supportedTypes to
let supportedTypes : [UTType] = [UTType.text]
It does let me access them. Does anyone have a solution for this? I obviously need the user to be able to select a folder in these applications... you can see why that is important.
This is up to the file provider extension (Google Drive, etc.). To allow picking a folder, the file provider has to lay content in its directory in a hierarchical manner... if they do this, they need to specify NSExtensionFileProviderSupportsPickingFolders in their Info.plist to tell the system it's allowed to choose folders.
Do you need to choose a save location and persist it? If yes, then you'll be blocked on the file provider implementing the necessary API. If not, the type you pass should the type of the document you are actually saving. The document will be saved once in the chosen folder (without any additional requirements on the file provider extension), and you will have to use the document picker again to save the next document.
If you are trying to select Dropbox as a location to import files from in the Apple File Importer but it does not advance to the file selection screen I found that restarting my iPhone seemed to resolve that issue.

Got an error when dragging files using NSEvent. (macOS)

I wanna drag files to my window and then perform actions.
I tried to use snippets below provided in this answer to distinguish whether you're dragging a file or a window.
// In my window controller
class MyWindowController: NSWindowController {
init() {
// Some initialization steps below are omitted
let win = NSWindow(...)
super.init(window: win)
let contentView = DropView(frame: win.frame)
win.contentView?.addSubview(contentView)
registerGlobalMouseEvent()
}
func registerGlobalMouseEvent() {
self.window?.acceptsMouseMovedEvents = true
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
// Codes below will cause errors
let pasteBoard = NSPasteboard(name: .drag)
guard let fileNames = pasteBoard.propertyList(forType: .init(rawValue: "NSFilenamesPboardType")) as? NSArray else { return }
let changeCount = pasteBoard.changeCount
if fileNames.count > 0 && lastChangeCount != changeCount {
lastChangeCount = changeCount
// My actions when dragging
}
})
}
}
Then I ran my codes and started dragging, I got three errors:
[sandbox] Failed to get a sandbox extension
[Framework] Failed to issue sandbox extension for /Users/roy/Downloads/test.txt with error 1
[default] Failed to issue sandbox token for URL: 'file:///Users/roy/Downloads/test.txt' with error: 'Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Cannot issue a sandbox extension for file "/Users/roy/Downloads/test.txt": Operation not permitted}'
 
But when I just do
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
// My actions
})
, then everything went fine.
 
The first error seems harmless since it didn't prevent my app from running.
The second and the third ones are deadly and directly caused my app to crash.
I wonder if there are any problems in his code? Any useful thoughts would be great! :)
 
You need to know about Bookmarks and Security Scoped URLs when working with sandbox . A dragged URL gives your app process permission just once to read or read/write a “user selected file” depending on how you configure entitlements.
You can save a bookmark (blob of data) to keep access over subsequent sessions as long as the file isn’t updated by another process at which point the bookmark becomes stale and you will need to encourage the user to select the file again.
Handing a URL to another process across an XPC boundary like sharing requires that you own the file so may involve a copy to your sandbox cache.
e.g:
let dragurl = url_of_dragged_file //at this point you have at-least read access
let cachepath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last!
let cachedir = URL(fileURLWithPath: cachepath)
let cacheurl = cachedir
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension(dragurl.pathExtension)
try FileManager.default.copyItem(at: dragurl, to: cacheurl)
At this point you have a copy in your local sandbox cache that can be handed off to a share sheet.
So I finally got a solution for this. :)
It appears that it indeed have something to do with the snippets I mentioned above, and here's the correction:
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
let pasteboard = NSPasteboard(name: .drag)
let changeCount = pasteboard.changeCount
if lastChangeCount != changeCount {
lastChangeCount = changeCount
if pasteboard.canReadObject(forClasses: [NSURL.self], options: [:]) {
/// actions
}
}
})
In this way, I got no errors and my codes run perfectly!

Facebook manual login flow with Wkwebview fails in macOS App

WKWebView doesn't do the "login from Facebook" job as wanted, because the browser seems to always remember the account credentials previously used to login, even though I tried to clear cookies both before and after the login requests are made.
I'm manually building a Facebook login flow for a MacOS project, following the guide here:
https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/
I decided to use a embedded web browser (WKWebView) to handle the login. What I wanted to achieve is that, when user clicks on the "log in from Facebook" button, the WKWebView gets presented as a sheet, loads the https://www.facebook.com/v3.3/dialog/oauth?client_id=[my-app-id]&redirect_uri=https://www.facebook.com/connect/login_success.html&response_type=token request, shows the dialog to let the user enter his email and password, and, after the user successfully logs in and the it gets the redirect_uri with a token, closes itself to finish the login flow.
For the first time this procedure gets executed on a new computer, it works fine. But once I successfully login as one user, the browser seems to automatically remember the account credentials, and next time I click on "log in from Facebook", it shows and then closes immediately, which means that it directly jumps to the redirect_uri with the previous user's token appended to the uri.
I don't have much prior knowledge about how program works with caches or how it stores user credentials, but I assumed it has something to do with cookies. So I did
HTTPCookieStorage.shared.removeCookies(since: .distantPast)
and
facebookLoginRequest.httpShouldHandleCookies = false
at where I think is suitable.
These operations made a difference but still don't solve the problem. The browser doesn't close itself "right after" it's presented any more, but still, it might get the redirect-uri with the token of the previous user after 10-ish seconds and then closes itself (interrupting the ongoing credential entering behavior of the current user) and logs into my app as the previous user.
I then inspected what requests the program was loading (how it jumps between url's), and here it is:
1. https://www.facebook.com/v3.3/dialog/oauth?client_id=2390673297883518&redirect_uri=https:// www.facebook.com/connect/login_success.html&response_type=token
2. https://www.facebook.com/login.php?skip_api_login=1&api_key=2390673297883518&kid_directed_site=0&app_id=2390673297883518&signed_next=1&next=https%3A%2F%2Fwww.facebook.com%2Fv3.3%2Fdialog%2Foauth%3Fclient_id%3D2390673297883518%26redirect_uri%3Dhttps%253A%252F%252Fwww.facebook.com%252Fconnect%252Flogin_success.html%26response_type%3Dtoken%26ret%3Dlogin%26fbapp_pres%3D0%26logger_id%3Da6eb9f6e-66d4-41e3-a5f2-dc995c98183e&cancel_url=https%3A%2F%2Fwww.facebook.com%2Fconnect%2Flogin_success.html%3Ferror%3Daccess_denied%26error_code%3D200%26error_description%3DPermissions%2Berror%26error_reason%3Duser_denied%23_%3D_&display=page&locale=en_US
3. https://www.facebook.com/common/referer_frame.php
4. https://www.facebook.com/v3.3/dialog/oauth?client_id=2390673297883518&redirect_uri=https%3A%2F%2Fwww.facebook.com%2Fconnect%2Flogin_success.html&response_type=token&ret=login&fbapp_pres=0&logger_id=a6eb9f6e-66d4-41e3-a5f2-dc995c98183e
5. https://www.facebook.com/login/device-based/regular/login/?login_attempt=1&next=https%3A%2F%2Fwww.facebook.com%2Fv3.3%2Fdialog%2Foauth%3Fclient_id%3D2390673297883518%26redirect_uri%3Dhttps%253A%252F%252Fwww.facebook.com%252Fconnect%252Flogin_success.html%26response_type%3Dtoken%26ret%3Dlogin%26fbapp_pres%3D0%26logger_id%3Da6eb9f6e-66d4-41e3-a5f2-dc995c98183e&lwv=100
6. https://www.facebook.com/v3.3/dialog/oauth?client_id=2390673297883518&redirect_uri=https%3A%2F%2Fwww.facebook.com%2Fconnect%2Flogin_success.html&response_type=token&ret=login&fbapp_pres=0&logger_id=fa7e695e-3469-43f5-9b79-75c6130824b0&ext=1564027752&hash=AebnuofbfCPezaKn
7. https://www.facebook.com/connect/login_success.html#access_token=EAAXXXX&data_access_expiration_time=1571800153&expires_in=5104236
1, 2, 3 always take place. 4 is what's loaded when the browser interrupts you, automatically logs you as the previous user, and closes itself. It doesn't always happen, or at least it doesn't always happen after the same amount of time, so it's pretty unpredictable to me. I think it might have something to do with clearing cookies, but I don't know how.
If 4 is loaded, 5 and 6 never gets to get loaded, and we directly jump to 7 (that has the access token of the previous user), which is what should be loaded after a complete, successful login.
Then what I did was trying to block 4.
if let urlStr = navigationAction.request.url?.absoluteString {
let block = urlStr.range(of: "&ret=login&fbapp_pres=0&logger_id=")
let allow = urlStr.range(of: "&hash=")
if (block != nil && allow == nil) {
print("not ok")
print(urlStr)
decisionHandler(.cancel)
} else {
print("should ok")
print(urlStr)
decisionHandler(.allow)
}
So 4 never gets loaded. I got to complete the "entering my email and password and click the login button" action every time. But this still doesn't solve the problem, as, like a little fewer than half of all times, after I click on login, Facebook gives the following message: "Something went wrong. Try closing the browser and reopen it." Then I'll have to quit my application and reopen it, and sometimes it works fine, sometimes it still doesn't, so I'll need to quit and reopen and again.
When I click on the login button, 5 always gets loaded. But when it succeeds, 6 and 7 also gets loaded; when it fails, it gets stuck at 5, doesn't get to 6 and 7, and pops up the "something went wrong" message.
Ccreen shot of the something went wrong message:
As you can see in the screen shot, under the "something went wrong" message, my Facebook home page is also loaded. However, the loaded Facebook page doesn't necessarily belong to the user whose credential I just entered before I clicked on login but to the previous user (I tested using multiple accounts). If the loaded Facebook home page and the user who just tried to login mismatch, Facebook can detect this mismatch and then pop up a "please login first" message and force the user back to a login dialog.
This means that even though I tried to block 4, it didn't really prevent the program from logging itself in as the previous user; it probably only prevented the URL from loading and displaying in the user interface. I also searched online and it's said that the "something went wrong" message is shown when a user is already logged in but another login request is made again.
So I guess the core problem that's troubling me is still, how on earth can I get the program to stop storing the credentials of the previous user? How does this storing mechanism even work? Thanks so much in advance for anyone who would help me with solving this problem. I have poor networking knowledge and what happens here after the initial HTTP request was made looks like a big black box to me and I feel hopeless lol
Here's the entire class I've written to try to handle the login flow for reference:
import Cocoa
import WebKit
class signInWeb: NSViewController, WKNavigationDelegate {
var token = ""
var gotToken = false
static var instance : signInWeb?
var request : URLRequest?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
self.view.setFrameSize(NSSize.init(width: 1200, height: 900))
let urlString = NSString(format: NSString.init(string : "https://www.facebook.com/v3.3/dialog/oauth?client_id=%#&redirect_uri=%#&response_type=token"), Facebook.AppId, "https://www.facebook.com/connect/login_success.html") as String
let facebookUrl = URL(string: urlString)
var facebookLoginRequest = URLRequest.init(url: facebookUrl!)
self.request = facebookLoginRequest
HTTPCookieStorage.shared.removeCookies(since: .distantPast)
facebookLoginRequest.httpShouldHandleCookies = false
signInWebView.load(facebookLoginRequest)
signInWebView.navigationDelegate = self
let loadedColor = ColorGetter.getCurrentThemeColor()
backButton.font = .labelFont(ofSize: 15)
if loadedColor != ThemeColor.white {
backButton.setText(str: "Back", color: loadedColor)
} else {
backButton.setText(str: "Back", color: .black)
}
signInWeb.instance = self
}
#IBOutlet weak var signInWebView: WKWebView!
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
//HTTPCookieStorage.shared.removeCookies(since: .distantPast)
if let urlStr = navigationAction.request.url?.absoluteString {
let block = urlStr.range(of: "&ret=login&fbapp_pres=0&logger_id=")
let allow = urlStr.range(of: "&hash=")
if (block != nil && allow == nil) {
print("not ok")
print(urlStr)
decisionHandler(.cancel)
} else {
print("should ok")
print(urlStr)
decisionHandler(.allow)
}
let components = urlStr.components(separatedBy: "#access_token=")
if components.count >= 2 {
let secondHalf = components[1]
let paramComponents = secondHalf.components(separatedBy: "&")
let token = paramComponents[0]
self.token = token
gotToken = true
let parent = self.parent as! SignIn
parent.handleCollapse()
}
}
}
#IBAction func goBack(_ sender: HoverButton) {
let parent = self.parent as! SignIn
parent.handleCollapse()
}
#IBOutlet weak var backButton: HoverButton!
func changeButtonColor(color : NSColor) {
if color != ThemeColor.white {
backButton.setText(str: "Back", color: color)
} else {
backButton.setText(str: "Back", color: .black)
}
}
}
Answering my own question:
First I tried Kubee's approach, but weird thing is that it didn't work out as I found that the cookies weren't stored into that exact WkWebView's httpCookieStore that I configured for it upon initialization but still into the WKWebsiteDataStore.default(). So I ended up clearing cookies in WKWebsiteDataStore.default() and it worked. I think it had to have something to do with me assembling my code in some way wrong, but I couldn't yet figured out why.
At first, I tried to edit the configuration.websiteDataStore property of the IBOutlet, but then I searched the documentation and realized that the configuration is a #NSCopying var, which means that when we call this property we only get the value but not the pointer. I figured maybe that was why changing the configuration to a .nonPersistent() didn't work out. I then removed the IBOutlet completely and initialized the WkWebView programmatically, but it still didn't work out.
Another confusion that I still had is that, as the 7 URL's that I included for reference in my original question was loading, facebook sent a couple of cookies. The cookies are not sent in just one URL but distributed in multiple of the URL's, so I had to clear the cookies from the WKWebsiteDataStore.default() every time a new request is going to load. Otherwise it doesn't work. Not sure about how those cookies work together,.
If you want to wipe all cookies, you can try this:
lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.websiteDataStore = WKWebsiteDataStore.nonPersistent()
return WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
}()
If you only want to wipe session cookies, you can try this:
lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.processPool = WKProcessPool()
return WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
}()
EDIT:
I had one person demote my examples with no explanation why. But perhaps I was misleading. The second example will usually do nothing since each web-view gets its own process pool by default unless you instantiate a web-view while another one is already open (somehow apple will re-use the same process pool). What it does reveal, however, is that you may share a process pool between different web views to share session cookies between them (the opposite of clearing session cookies) and as soon as you want to clear session cookies you destroy that process pool and create a new one. So if you want to share session cookies between different web views you simply have to do the following:
extension WKProcessPool {
static var shared = WKProcessPool()
}
lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.processPool = WKProcessPool.shared
return WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
}()
And to clear session cookies you would do:
WKProcessPool.shared = WKProcessPool()
And now next time you create a webView by the lazy example above you will have a brand new process pool and your session cookies will be wiped.

Finder Share Extension: Getting a preview image from NSItemProvider

I'm creating a Finder Share extension to be used when selecting a file. I'd like to show an image preview of the file that you are sharing (like you see in the Messages and Twitter Finder Share extensions, for example).
In the loadView method of the view controller for my share extension, I'm doing this:
let item = self.extensionContext!.inputItems[0] as! NSExtensionItem
if let attachments = item.attachments as? [NSItemProvider] {
if let attachment = attachments.first {
attachment.loadPreviewImage(options: nil, completionHandler: { (item, error) in
if error != nil {
//handle error...
} else if let img = item as? NSImage {
//put image preview in my "share sheet"...
}
})
}
}
The problem is that when I keep hitting the error condition. The error that I'm getting is:
Error Domain=NSItemProviderErrorDomain Code=-1000 "Cannot load preview." UserInfo={NSLocalizedDescription=Cannot load preview.}
For what it's worth, I am able to call attachment.loadItem() successfully and work with the file. But I don't really care to do anything with the file itself at this point, I just want a thumbnail image that represents the file which theoretically this method should give me...
Any ideas?
I am in the same situation. After read the manual, I think here is the reason.
"Loads the preview image for the item that is represented by the item provider."
However, it is not forced that the item provider must provides a preview. So the error simply says that there is no preview that the item provider can provide. You should do it yourself.

how to open an app from Mac from Xcode project (Cocoa App)

Today I have created an Cocoa Application for the very first time . I want to create a simple App which will open an APP from my Mac , if the file not found it will show a Link in a Label to download the App. Here is my code below which I am struggling with .
if let fileCheck = NSURL.fileURL(withPath: "/Applications/Mango.app") {
if NSWorkspace.shared().open(fileCheck as URL) {
print("url successfully opened")
}
} else {
self.downloadLink.insertText("Invalid Path")
}
NSURL.fileURL(withPath: "/Applications/Mango.app") giving me Conditional Binding Must be Optional , I don't know how to fix that. And I am struggling with how to show a link on my Label either. Any kind hearted Dev please help.
got the solution by someone , I don't know why he deleted that !
if FileManager().fileExists(atPath: "/Applications/Mango.app") {
NSWorkspace.shared().open(NSURL(fileURLWithPath: "/Applications/Mango.app") as URL)
} else {
downloadLink.isHidden = false
}