Swift - Widget not able to access app group - ios

I have lately been trying to make a widget and wanted to share a piece of data between my widget and my app.
static var sharedDataFileURL: URL {
let appGroupIdentifier = "group.com.unknownstudios.yk"
guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
else { preconditionFailure("Expected a valid app group container") }
return url.appendingPathComponent("userID.plist")
}
The above code works fine on my app, but when I run it on my widget it gives me the following output. I have checked that the app group is correct and is active on both the iOS app and the widget.
[unspecified] container_create_or_lookup_path_for_platform: client is not entitled
[unspecified] container_create_or_lookup_app_group_path_by_app_group_identifier: client is not entitled
Fatal error: Expected a valid app group container: file WidgetExtension/Library.swift, line 143
Edit:
I have also tried to use UserDefaults, but they don't work either.
The following is the way I use the the FileManager and UserDefaults
UserDefaults(suiteName: "group.com.unknownstudios.yk")!.set("**USERID**", forKey: "userID")
let data = Data("**USERID**".utf8)
do {
try data.write(to: URL.sharedDataFileURL, options: .atomic)
} catch {
print(error.localizedDescription)
}
And the following is how I try to read the data from the widget:
var userID: String? = {
if let userid = UserDefaults(suiteName: "group.com.unknownstudios.yk")!.string(forKey: "userID") {
print(userid)
return userid
} else if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.unknownstudios.yk") {
if let data = try? Data(contentsOf: url.appendingPathComponent("userID.plist")), let string = String(data: data, encoding: .utf8) {
return string
}
}
return nil
}()
WidgetExtension app groups:
Main app, app groups:

I found out what my problem was. I had accidentally selected the release tab under Signing & Capabilities. Which caused my app group to be wrong when checking on release, thus making the error occur.
I just had to select All and re-add the app groups capability which made it work again.

Related

Add card to Wallet in Unity

I need add card to apple Wallet in Unity.
For example - I am already generated pkpass file (it could storage in resources) and now I need only button in my app, what can added this card to Wallet.
I have read this manual and find only code for swift:
guard let passPath = Bundle.main.path(forResource: "wallet", ofType: "pkpass") else { return }
let error: ErrorPointer = ErrorPointer(nilLiteral: ())
guard let passData = NSData(contentsOfFile: passPath) else { return }
let pass = PKPass(data: passData as Data, error: error)
let passLibrary = PKPassLibrary()
passLibrary.addPasses([pass]) { (status) in
print(passLibrary.containsPass(pass))
}
As I understood, I need implement PKPassLibrary in Unity and add this method (with some modifications) to button click event.
Could you help me with this moment, please?
The situation is difficult by the fact that I don't have a Mac and IOs device.

Auto login using UserDefaults() not working Swift 5

I have followed some tutorials and used their methods to implement auto login for my app, but once I relaunch the app after entering the credentials, the app does not log in.
var userDefaults = UserDefaults.standard
here I initiate the user defaults feature
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let safeData = data {
if let dataString = String(data: safeData, encoding: String.Encoding.utf8) {
print(dataString)
if dataString == "Hello" {
self.userDefaults.setValue(true, forKey: "UserIsLoggedIn")
DispatchQueue.main.async {
self.performSegue(withIdentifier: "loginSegue", sender: self)
}
} else {
DispatchQueue.main.async {
self.validationLabel.isHidden = false
self.validationLabel.text = " Username or password is incorrect. "
self.loginSuccessful = false
}
}
}
} else {
print(error ?? "Error with data API URLSession")
}
}.resume()
here, inside the API call. if the response from the API is "hello" which means the login was successful, i set the value to true with an identifier.
if userDefaults.value(forKey: "UserIsLoggedIn") as? Bool == true {
performSegue(withIdentifier: "loginSegue", sender: self)
} else {}
here in the view did load I use the userDefaults to perform the segue to the next screen for future launches.. but it is not working.
initiation
viewdidload
API call
var userDefaults = UserDefaults() seems wrong. Use let userDefaults = UserDefaults.standard instead. Also there's no need to make this a property of your class, simply use this within your methods whereever it's needed.
Swift 5. Auto login myApplication According woking & help you best my try
//First Time key
// According API Response success then add
if !UserDefaults.standard.bool(forKey:"isLogin") { // Success
UserDefaults.standard.set(true, forKey: "isLogin") // To do....
UserDefaults.standard.synchronize()
} else { // response fail
// To do ....
}
Have you checked if the value is being saved in your User Defaults file? If you are running your app on the simulator, try printing the file path to your User Defaults file and try locating it. It might not fix your immediate problem but it will hopefully give you an idea as to where the problem is coming from.
Try printing the location of your User Defaults file using the following line in your AppDelegate.swift
print(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last! as String)

Realm file management

We have been using Realm for a while and some of our users have been experiencing some data loss related to Realm. We think we have narrowed it down to our compaction method for when the file gets too big. We would like to ask for a little advice on if this is the proper way to recreate our Realm file. This method is called on applicationDidEnterBackground.
We wrote a sample of what we are doing below:
public static func compact() {
// Get the original file path
let configuration = RealmSampleClient.shared.config
guard let originalFileURL = configuration.fileURL else {
return
}
// check if the file size is bigger than 10mb, if not return
guard let attr = try? FileManager.default.attributesOfItem(atPath: originalFileURL.absoluteString),
let fileSize = attr[FileAttributeKey.size] as? UInt64,
fileSize > 500_000_000 else {
return
}
// create a filepath for a copy
let date = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMddHHmmss"
let dateString = "\(dateFormatter.string(from: date)).realm"
let copyFileURL = originalFileURL.deletingLastPathComponent().appendingPathComponent(dateString)
// copy the Realm file
do {
let realm = try Realm(configuration: configuration)
try realm.writeCopy(toFile: copyFileURL, encryptionKey: configuration.encryptionKey)
} catch {
return
}
// remove the old file and copy the new one
do {
removeRealmFile(at: originalFileURL)
try FileManager.default.copyItem(at: copyFileURL, to: originalFileURL)
} catch {
}
// remove a copy if it exists
guard FileManager.default.fileExists(atPath: copyFileURL.path) else { return }
do {
try FileManager.default.removeItem(at: copyFileURL)
} catch {
}
}
private static func removeRealmFile(at url: URL = databaseUrl) {
let realmURLs = [
url,
url.appendingPathExtension("lock"),
url.appendingPathExtension("note"),
url.appendingPathExtension("management"),
]
realmURLs.forEach { URL in
guard FileManager.default.fileExists(atPath: URL.path) else { return }
do {
try FileManager.default.removeItem(at: URL)
} catch {
}
}
}
Thanks your your help
I can see no kind of compacting code here, only a copy of the database file. So I assume you have left that out to keep the code compact here.
Anyway, you do this operation when the app enters background mode. Do you register a background task for that? If the compacting operation takes too much time the task gets killed by iOS, I think this is the problem.
You can explicitly ask the OS for more background execution time with UIApplication.shared.beginBackgroundTask but this is also a very time limited amount, usually 3 minutes.
But this is all digging in the dark, you should post more code to see how your background task is set up.
As per Realm doc, it's recommended to wrap your code with autoreleasepool, like this
do {
let realm = try Realm(configuration: configuration)
try realm.writeCopy(toFile: copyFileURL, encryptionKey: configuration.encryptionKey)
} catch {
return
}
and doing this in a backgroundtask will definitely help, a friendly advice is to always handle errors, you are just returning in the catch block, a log may help ..
Looking more into the doc I can see that RealmSwift integrates a compacting feature now, more details here :
https://realm.io/docs/swift/latest/#compacting-realms
We refactored the flow of our app a bit and it has seemed to solve our problems. It was related to accessing realm too soon during the startup process, possible on multiple threads.

iOS 11 Share Extensions Broken

Before iOS 11 came out I created a share extension to my social media app. It worked perfectly fine. Once iOS 11 came out, the share extension quit working. I searched and debugged the extension until I found the source of the problem. When looping through the attachments inside the extensionContext.inputItems[0].attachments, none of the attachments has an item conforming to kUTTypeImage. So none of my code was running from that point on. I also had another strange outcome yesterday. This is part of my code inside the didSelectPost function.
guard let content = extensionContext?.inputItems[0] as? NSExtensionItem else { return }
guard let contentAttachments = content.attachments as? [NSItemProvider] else { return }
let skyName = self.textView.text
for attachment in contentAttachments {
if attachment.hasItemConformingToTypeIdentifier(imageType) {
attachment.loadItem(forTypeIdentifier: imageType, options: nil) { (data, error) in
guard error == nil, let url = data as? NSURL else { return }
self.imageFromAsset(url: url as URL)
if !self.selectedType.isEmpty {
do {
let imageData = try Data(contentsOf: url as URL)
self.skyImage = UIImage(data: imageData)
self.saveSkyImage()
guard let skyOriginalImageURL = self.skyOriginalImageURL else { return }
guard let skyImageURL = self.skyImageURL else { return }
let newSky = Sky(name: skyName ?? "Another Sky",
type: self.selectedType,
date: self.date,
location: self.location,
picture: CKAsset(fileURL: skyImageURL),
likes: 0, flags: 0,
likedBy: [CKReference](), flaggedBy: [CKReference](),
originalImage: CKReference(record: CKRecord(recordType: "SkyImage"), action: .none))
let newSkyImage = SkyImageFullResolution(picture: CKAsset(fileURL: skyOriginalImageURL))
self.saveSky(sky: newSky, skyImage: newSkyImage)
} catch {
print(error.localizedDescription)
self.closePostWindow()
}
}
}
}
}
defer {
closePostWindow()
}
I don't have a direct answer to your problem but recently in iOS 11 I have resolved a problem to display a share extension involving PDF files.
My problem was that my expected type identifiers were not found among the attachments.
NSItemProvider has an instance property registeredTypeIdentifiers to show you the type identifiers that can be found when your extension is activated.
That's what I do :
1) I use TRUEPREDICATE as NSExtensionActivationRule to force display my share extension in the context of my interest.
2) After you select your share extension, your extension code will be triggered.
Then you print all the type registeredTypeIdentifiers of each attachment, by looping through your contentAttachments.
Once you have identified all the identifiers, you will be able to find a solution to your problem.

Two apps in one device and the image uploaded from first app will appear in the second app

I want to make two iOS apps say A and B. Then what I need to do is to upload 5 images from user's gallery in A app and show those 5 images in app B. I tried this with Document Directory but then I came to know that each app has it's own document directory and any other app does not have access to that document directory. Now I don't know how to do this. Any help?
Sorry forgot to mention that I want it done offline, so I can't use an api in both the apps.
Yes you can share some data within groups of app as per new updates in iOS 8.0 and above.
Just you need to create apps group and share your data among that groups app.
Below are few steps to enable app groups.
Step1: Enable App group from Target Setting -> Capabilities -> App Group
Sample Group name : groups.companyname or groups.appgroupname
Note: Every app which you want in this group must have same group name
in this section.
Step 2: Get General sharing folder where you can store shared data like images DB.
for that you need to get path of that shared folder as mention below
Objective-C :
- (NSString *)getSharedGroupFolderPath:(NSString*)aAppGroupName {
NSURL *aContainerPathURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:aAppGroupName];
return [aContainerPathURL relativePath];
}
Swift:
static func appGroupContainerURL() -> URL? {
// 1
let fileManager = FileManager.default
guard let groupURL = fileManager
.containerURL(forSecurityApplicationGroupIdentifier: <APP_GROUP_NAME>) else {
return nil
}
let storagePathUrl = groupURL.appendingPathComponent("File Provider Storage")
let storagePath = storagePathUrl.path
// 2
if !fileManager.fileExists(atPath: storagePath) {
do {
try fileManager.createDirectory(atPath: storagePath,
withIntermediateDirectories: false,
attributes: nil)
} catch let error {
print("error creating filepath: \(error)")
return nil
}
}
// 3
return storagePathUrl
}
By above method you can get Group container folder path now save your data here which you need to share among multiple Apps.
Step3:
Store your image of file on container path like below
guard let destinationURL = TestFileViewController.fileUrlForDocumentNamed("FileName") else {
return
}
let image = UIImage.init(named: "tests")
if !FileManager.default.fileExists(atPath: destinationURL.path) {
do {
try UIImageJPEGRepresentation(image!, 1.0)!.write(to: destinationURL)
print("file saved")
} catch {
print("error saving file")
}
} else {
print("file already exists")
}
Use below supported method for getting file path :
// Returns the file URL for the file to be saved at
static func fileUrlForDocumentNamed(_ name: String) -> URL? {
guard let baseURL = appGroupContainerURL() else { return nil }
let protectedName: String
if name.isEmpty {
protectedName = "Untitled"
} else {
protectedName = name
}
return baseURL.appendingPathComponent(protectedName)
.appendingPathExtension("your file extension")
}
Hope this will helps you to achieve your requirements.
You can use group app. Project Navigator -> Capabilities -> Turn on group apps. The groups name entailments must be same name and also need to enable in your developer account. Now you create a directory to shared space then you will access the data or image from both.
//use this code for create share directory
let fileManager = FileManager.default
guard let groupUrl = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "your app group name") else {
return nil
}
var storagePathUrl = groupUrl.appendingPathComponent("FileProviderStorage")
let storagePath = storagePathUrl.path
if !fileManager.fileExists(atPath: storagePath) {
do {
try fileManager.createDirectory(atPath: storagePath, withIntermediateDirectories: false, attributes: nil)
} catch _ {
}
} else {
print("File Path: \(storagePath)")
}

Resources