IOS share extension how to support apple news - ios

Can you please help me in the matter of supporting apple news sharing ,
My Share Extension info.plist contains :
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsAttachmentsWithMaxCount</key>
<integer>10</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
<integer>10</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
How can i see my share extension while share some thing from apple news ?

OK I sorted this out. You need to configure your Extension to allow content for both public.plain-text and public.url types. Apple News sends an ItemProvider with two attachments, first a plain-text piece with the article summary, and second a Web URL to the article itself. You must accept and process both.
Try these extension attributes. They use a predicate to find the required URL type attachment (assuming that's what you want):
<key>NSExtensionActivationDictionaryVersion</key>
<integer>2</integer>
<key>NSExtensionActivationUsesStrictMatching</key>
<integer>2</integer>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>SUBQUERY(extensionItems, $e, (
SUBQUERY($e.attachments, $a, ANY $a.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url").#count == 1
)).#count == 1
            </string>
<key>RequestsOpenAccess</key>
<true/>
</dict>
And code along these lines to find the proper URL attachment, again, assuming that's the bit you want:
NSExtensionItem *inputItem = self.extensionContext.inputItems.firstObject;
NSItemProvider *itemProvider;
for (itemProvider in [inputItem.userInfo valueForKey:NSExtensionItemAttachmentsKey]) {
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *) kUTTypeURL]) {
break;
}
}
if (!itemProvider) {
// Handle error here
return;
}
[itemProvider loadItemForTypeIdentifier:(NSString *) kUTTypeURL options:nil completionHandler:^(NSURL *url, NSError *error) {
// Handle the URL here
}];

Here's my rough Swift 4 version I'm using together with your magic PLIST. Seems to work in both News and Safari.
func getUrl(callback: #escaping ((URL?) -> ())) {
guard let items = extensionContext?.inputItems,
let item = items.first as? NSExtensionItem,
let attachments = item.attachments else {
callback(nil)
return
}
var found = false
for attachment in attachments {
if let provider = attachment as? NSItemProvider {
if provider.hasItemConformingToTypeIdentifier("public.url") {
found = true
provider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in
if let shareURL = url as? URL {
callback(shareURL)
} else {
print("error getting url: \(error)")
callback(nil)
}
}
}
}
}
if !found {
callback(nil)
return
}
}

Related

azure ad login is not working in ios app while microsoft authenticator app is installed

I have built an ios app which is using azure ad login and it was working fine but after install microsoft authenticator app the azure ad login is not working anymore
in fact the alert which does say " app wants to use 'microsoftonline.com' to sign in"
not coming
and uninstalling the authenticator my ios app can login via azure ad again
let kClientID = [clientid]
let kGraphEndpoint = "https://graph.microsoft.com/"
let kAuthority = "https://login.microsoftonline.com/xxxxxxxxx"
let kRedirectUri = [URI]
let kScopes: [String] = ["user.read"]
func initMSAL() throws {
guard let authorityURL = URL(string: kAuthority) else {
print("Unable to create authority URL")
return
}
let authority = try MSALAADAuthority(url: authorityURL)
let msalConfiguration = MSALPublicClientApplicationConfig(clientId: kClientID,
redirectUri: kRedirectUri,
authority: authority)
self.applicationContext = try MSALPublicClientApplication(configuration: msalConfiguration)
self.initWebViewParams()
}
func initWebViewParams() {
self.webViewParamaters = MSALWebviewParameters(authPresentationViewController: self)
}
func acquireTokenInteractively() {
guard let applicationContext = self.applicationContext else { return }
guard let webViewParameters = self.webViewParamaters else { return }
let parameters = MSALInteractiveTokenParameters(scopes: kScopes, webviewParameters: webViewParameters)
parameters.promptType = .selectAccount
applicationContext.acquireToken(with: parameters) { (result, error) in
if let error = error {
print("Could not acquire token: \(error)")
return
}
guard let result = result else {
print("Could not acquire token: No result returned")
return
}
self.accessToken = result.accessToken
let aduser=MicrososftUser.init(id: result.uniqueId ?? "", mail: result.account.username ?? "", givenName: result.account.username ?? "", surname: "");
self.adLoginRequest(aduser: aduser)
//self.getContentWithToken()
}
}
info.plist
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>msauth.$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.674973595907-gsm9poebb8u1vvb28rvt7osv</string>
</array>
</dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>msalv2</string>
<string>msaalv3</string>
<string>msauthv2</string>
<string>msauthv3</string>
</array>
<key>UIAppFonts</key>
<array>
<string>Inter.ttf</string>
<string>Inter-Black.ttf</string>
<string>Inter-ExtraLight.ttf</string>
<string>Inter-Regular.ttf</string>
<string>Inter-Bold.ttf</string>
<string>Inter-Light.ttf</string>
<string>Inter-SemiBold.ttf</string>
<string>Inter-ExtraBold.ttf</string>
<string>Inter-Medium.ttf</string>
<string>Inter-Thin.ttf</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
</plist>
when i click on azure ad login button the following error is showing in debug window
Could not acquire token: Error Domain=MSALErrorDomain Code=-50000 "(null)" UserInfo={MSALErrorDescriptionKey=Failed to delete broker key with error: -34018, MSALInternalErrorCodeKey=-42708, MSALCorrelationIDKey=4A0C2756-0173-7068-AC4F-AFEC1C84BCB3}
could any one help me with this issue
sorry for my bad english
solving the issue by disabling the access from my app to authenticator.
Adding the following line in initMSAL function
MSALGlobalConfig.brokerAvailability = .none
got help from following links:
https://github.com/AzureAD/microsoft-authentication-library-for-objc/issues/845
https://github.com/AzureAD/microsoft-authentication-library-for-objc/blob/dev/MSAL/src/public/configuration/MSALGlobalConfig.h#L74

App's created Folders/Files don't show up in "Files" on iPhone

wonder if anyone can help me. I have an app and I'm trying to move some files into iCloud so they'll show up in "Files" and cloud to other devices. I've been going through lots of resources online researching what's wrong, and nothing seems to help.
In my app project, I have turned on iCloud Documents in capabilities.
In my plist file, I have this:
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.com.mypublishername.myappname</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>myappname</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
</dict>
</dict>
In my entitlements file I have:
<dict>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.mypublishername.myappname</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.com.mypublishername.myappname</string>
</array>
</dict>
in ObjC, I'm fetching the iCloud folder like so:
NSURL *rootDirectory = [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]URLByAppendingPathComponent:#"Documents"];
if (rootDirectory)
{
if (![[NSFileManager defaultManager] fileExistsAtPath:rootDirectory.path isDirectory:nil]) [[NSFileManager defaultManager] createDirectoryAtURL:rootDirectory withIntermediateDirectories:YES attributes:nil error:nil];
gCloudFolder=rootDirectory;
}
Then, when I save a file, I do so locally, and move it into the cloud folder like this:
//
// theFilename is a file in the app's documents folder...
//
int aFile=creat(theFilename,S_IREAD|S_IWRITE);close(aFile);
aFile=open(theFilename,O_BINARY|O_RDWR);
if (aFile)
{
write(aFile,theDataPtr,theLen);
close(aFile);
if (gCloudFolder)
{
NSURL *aLocalStr=[NSURL fileURLWithPath:[NSString stringWithUTF8String:theFilename]];
NSURL *aCloudStr=[gCloudFolder URLByAppendingPathComponent:#"testing_file.txt"];
NSError *error;
if (![[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:aLocalStr destinationURL:aCloudStr error:&error]) NSLog(#"iCloud Error occurred: %#", error);
}
So... what happens. This file DOES get created. If I run this twice, it tells me it can't move to testing_file.txt because it already exists. Also, if I try to setUbiquitous:NO on the file, it tells me I can't set it to no when the file hasn't been synced.
Any idea why my app's folder and this file don't show up in my FILES folder under iCloud?
I have increased the bundle version, which is something I've seen elsewhere. Did nothing.
What am I doing wrong?
This completely stunned me; I had no idea it was possible. I'll just describe my test app in full. It's going to look a lot like yours!
Here is the bundle identifier:
Here is the entitlement:
Here is the entitlement text:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.neuburg.matt.SaveIntoFilesApp</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.com.neuburg.matt.SaveIntoFilesApp</string>
</array>
</dict>
</plist>
Here is the entry in the Info.plist:
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.com.neuburg.matt.SaveIntoFilesApp</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>MyApp</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
</dict>
</dict>
Here is the app delegate:
var ubiq : URL!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
DispatchQueue.global(qos:.default).async {
let fm = FileManager.default
let ubiq = fm.url(forUbiquityContainerIdentifier:nil)
print("ubiq: \(ubiq as Any)")
DispatchQueue.main.async {
self.ubiq = ubiq
}
}
return true
}
Here is the button I tap:
#IBAction func doButton (_ sender:Any) {
if let del = UIApplication.shared.delegate as? AppDelegate {
if let ubiq = del.ubiq {
do {
let fm = FileManager.default
let docs = ubiq.appendingPathComponent("Documents")
try? fm.createDirectory(at: docs, withIntermediateDirectories: false, attributes: nil)
let url = docs.appendingPathComponent("test.txt")
print("here we go")
try? fm.removeItem(at: url)
try "howdy \(Date())".write(to: url, atomically: true, encoding: .utf8)
print("saved")
} catch {
print(error)
}
}
}
}
I did have to increment the bundle version (from 1 to 2) and I did have to kill and restart the Files app. And then I saw my file (and can open and examine it):

Sharing attachments from Mail App not working in iOS 13

I'm working on the app that has share extension.
Share attachment from Mail App with Share extension is not working.
But sharing attachments(pdf, doc, image etc.) from gmail app working fine.
My swift code
for attachment in contents{
if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { data, error in
//Do action for image
}
} else {
if let item = (filePicker!.types as [String]).first(where: { (item) -> Bool in attachment.hasItemConformingToTypeIdentifier(item)}){
attachment.loadItem(forTypeIdentifier: item, options: nil) { data, error in
//Do action for file
}
}
}
}
My NSExtensionActivationRule:
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsAttachmentsWithMaxCount</key>
<integer>1</integer>
</dict>
I never work with the sharing extensions before, so any knowledge that can be shared will be a great resource for me.
Thanks in advance!

How to get a iOS share extension to get web page content from Safari instead of URL

I have a working share extension written in swift. When I test a share operation from Safari, I always only get the URL type (kUTTypeURL).
What I want is to get some form of rendered version of what the user is looking at (PDF or HTML?). Using the URL and opening it in a webview is not workable due to authentication issues, etc.
I've tried many different activation rules with no change. Here is the current one I am using:
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>20</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>0</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
My controller looks like this - when run from Safari, it always only has one attachment type - the URL:
override func didSelectPost() {
if let item = extensionContext?.inputItems.first as? NSExtensionItem {
if let attachments = item.attachments {
for attachment: NSItemProvider in attachments {
if attachment.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) {
attachment.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil, completionHandler: { (data, error) in
// Do stuff with this content now
self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
})
}
if attachment.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (url, error) in
if let shareURL = url as? NSURL {
// Do stuff with your URL now.
}
self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
})
}
}
}
}
}
Other approaches I've seen use a javascript file to walk the DOM but have seen no good examples and I'm not clear on if this would help me in any case.
The key is in using this line in your info.plist file:
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionJavaScriptPreprocessingFile</key>
<string>Action</string>
<key>NSExtensionActivationRule</key>
...your should be fine...
</dict>
At NSExtensionJavaScriptPreprocessingFile you specify the name of the .js file that contains javascript file that must contain a global object named ExtensionPreprocessingJS. You should then search for the items conforming to kUTTypePropertyList in your code (and it seems that you do already that looking at your code).
This is a short list of what you should do, ask if you need something more and it is easy to find more data on the internet if you start from this too.

Error consuming REST API using swift on iOS

I'm new on iOS development, I was following this tutorial: https://grokswift.com/simple-rest-with-swift/ and I don't know why the following code always return "the placeholder" and I can't see errors on the console's output:
import Foundation
class Resolver{
func doSomething() -> String{
var result = "the placeholder"
print("inside doSomething")
let postEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"
guard let url = NSURL(string: postEndpoint) else {
print("Error: cannot create URL")
return "error here"
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
print("another thing")
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
//return "error here"
}
print("hereeeeeee ############")
guard error == nil else {
print("error calling GET on /posts/1")
print(error)
return
//return "error here"
}
// parse the result as JSON, since that's what the API provides
let post: NSDictionary
do {
post = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as! NSDictionary
} catch {
print("error trying to convert data to JSON")
return
//return "error here"
}
// now we have the post, let's just print it to prove we can access it
print("The post is: " + post.description)
result = post.description
// the post object is a dictionary
// so we just access the title using the "title" key
// so check for a title and print it if we have one
if let postTitle = post["title"] as? String {
print("The title is: " + postTitle)
}
})
task.resume()
return result
}
}
this is my info.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string></string>
<key>CFBundleExecutable</key>
<string></string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>LSApplicationCategoryType</key>
<string></string>
<key>CFBundleName</key>
<string></string>
<key>CFBundleDisplayName</key>
<string></string>
<key>CFBundleVersion</key>
<string></string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>httpbin.org</key>
<dict>
<key>NSThirdPartyExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>jsonplaceholder.typicode.com </key>
<dict>
<key>NSThirdPartyExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>CFBundleShortVersionString</key>
<string></string>
<key>CFBundleGetInfoString</key>
<string></string>
</dict>
</plist>
When I run the test I see this output:
Test Suite 'RestTest' started at 2016-03-03 15:38:49.364
Test Case '-[FoodTrackerTests.RestTest testResolver]' started.
inside doSomething
another thing
// I want to see the result: the placeholder
Test Case '-[FoodTrackerTests.RestTest testResolver]' passed (0.061 seconds).
Test Suite 'RestTest' passed at 2016-03-03 15:38:49.425.
Executed 1 test, with 0 failures (0 unexpected) in 0.061 (0.062) seconds
Test Suite 'Selected tests' passed at 2016-03-03 15:38:49.426.
Executed 1 test, with 0 failures (0 unexpected) in 0.061 (0.063) seconds
Why the result variable doesn't contains the post.description value?
As suggested in the comments, it looks like tests are completing before the HTTP call is returned (because the HTTP request is asynchronous) and thus you aren't seeing the results of the API call before the test run ends.
Have a look at XCAsyncTestCase and set up your tests to wait for the async callback instead.
The doSomething() method returns "the placeholder" value before the asynchronous call is finished. That's why you see it.

Resources