I'm trying to create a reminder calendar so I can add and delete reminders. It is actually working well on the devices I use (iPhone 5/4S/4) but on certain client devices which are still iPhones - I'm getting this error below about the account not supporting reminders.
Here is the workflow:
* Init the event store.
* Request permission (check its granted for Reminder types) (iOS6+) for lower we just init.
* Create a new calendar, local storage, type = Reminder
* Save calendar to get its Identifier.
Works most of the time, this appears on some devices -
Error Domain=EKErrorDomain Code=24 “That account does not support reminders.”
Permissions are granted and checked under Settings, Privacy, Reminders. I can't find anything in the docs about the conditions under which you'd get this error.
Thanks!
Not sure if you still need this but here is what i ran into.
First of all i'm pretty sure that reminders cannot be set on a calendar with a local source. I kept getting the "That account does not support reminders". Even after setting all non read only properties on the calendar before committing to the event store it still didn't work. The source needs to be calDav. Then I tried Devfly's response and it didn't work either but for a different reason. It kept fetching my gmail calendar which does not allow setting reminders (i think its actually read only for events and reminders). So i used the following code to get the actual iCloud source
for (EKSource *source in sources) {
NSLog(#"source %#",source.title);
if (source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:#"iCloud"]) {
localSource = source;
break;
}
}
This setting this source on my new reminders calendar worked for me. hope it helps someone
First, just a check: you are creating a "new calendar" (a whole calendar), not just a "new reminder", right?
Second: are you using iOS6? Reminders are available (in EventKit) only starting from iOS6: link
As commented by Jesse Rusak, this happens because you are probably creating the new calendar inside an account/source that doesn't support reminders.
How do you create the new calendar?
Do you set the source property?
the first thing you can try, is to loop all sources until you find a suitable one.
EKSourceTypeLocal supports reminders. iCal too.
Here a list of EKSourceType
typedef enum {
EKSourceTypeLocal,
EKSourceTypeExchange,
EKSourceTypeCalDAV,
EKSourceTypeMobileMe,
EKSourceTypeSubscribed,
EKSourceTypeBirthdays
} EKSourceType;
Find a suitable one:
// find local source for example
EKSource *localSource = nil;
for (EKSource *source in store.sources)
{
if (source.sourceType == EKSourceTypeLocal) // or another source type that supports
{
localSource = source;
break;
}
}
Then, create the new calendar setting the right source
EKCalendar *cal;
if (identifier == nil)
{
cal = [EKCalendar calendarForEntityType:EKEntityTypeReminder eventStore:store];
cal.title = #"Demo calendar";
cal.source = localSource;
[store saveCalendar:cal commit:YES error:nil];
}
Try and let me know
What solved my problem is not saving the calendar to the local source, but instead to EKSourceTypeCalDAV (iCloud). It works, and it's synced across all devices.
The local store may not support reminders. This is reproducible if iCloud is enabled.
This is the most reliable solution I could find, without hard-coding any assumptions:
let calendar = EKCalendar(forEntityType: .Reminder, eventStore: eventStore)
if eventStore.sources.count == 0 { // reproducible after Reset Content and Settings
calendar.source = EKSource()
}
else {
calendar.source = eventStore.defaultCalendarForNewReminders().source
}
eventStore.saveCalendar(calendar, commit: true)
Related
I encountered the following error in my swift project: https://github.com/firebase/firebase-ios-sdk/issues/4393. To overcome this issue I change the display name to the characters before the # in a users email like so:
var displayName = user.email!
if let atRange = displayName.range(of: "#") {
displayName.removeSubrange(atRange.lowerBound.. < displayName.endIndex)
}
if user.displayName!.count < 2{
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = displayName
changeRequest?.commitChanges {(err) in
if let err = err{
print(err)
}
}
}
This works when I first log in but if I log out and then back in again the display name reverts back to Optional(""). Why would it be doing this?
Edit
I created a workaround where I run the above code not only when a user is first created, but every time a user logs in, this seems excessive though and there must be a reason why the display name keeps getting overwritten.
This is not an error, this is intended behaviour from Apple's side. By default, you should be updating the Firebase user with the appropriate display name rather than relying on 3rd party information since these are only available on first login.
Simply checking if the name exists on initial login and is longer than 0 characters, not null, etc. then you can update the name with the following:
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = displayName
changeRequest?.commitChanges { (error) in
// ...
}
This is also documented within the git-issue you posted https://github.com/firebase/firebase-ios-sdk/issues/4393#issuecomment-612193703
Ultimately, you are logging into your app as a Firebase user, not an apple user. Apple is only Authenticating the process, so you must update the Firebase user where possible as Apple is highly inconsistent across all platforms.
Is there a way to implement the behaviour - participants reverse their acceptances of CKShares? After the participants accept the CKShares, they go over the shared and say oh no I don't want to contribute anything to the shared and I don't even want to keep the shared records in watch lists of their UI interface.
How do we do to remove them from such collaborations (from shared database to be exact)? The use case is very legitimate as people may accept somethings so that having a chance to go over a bit before decide wether they actually contribute their time and efforts into the shared.
I have tried to use CKModifyRecordZonesOperation to delete the shared zone of CloudKit shared database:
var zoneOp = CKModifyRecordZonesOperation(recordZonesToSave: nil, recordZoneIDsToDelete: [zoneID]
zoneOp.database = sharedDB
However, it seems any shared zones can't be deleted from any participants' iOS devices (except for using their development CloudKit Dashboards that may work though) as such attempt is followed by CKError - disallow delete zone...
From the docs: https://developer.apple.com/documentation/cloudkit/ckshare
If a participant attempts to delete the share, CloudKit removes the participant.
Deleting the share is simple:
try await container.sharedCloudDatabase.deleteRecord(withID: shareRecordId)
The question is how to get the original CKShare.recordID?
Zone-wide-share
Re-create the share id of the zone by using the zoneID:
let shareRecordID = CKRecord.ID(
recordName: CKRecordNameZoneWideShare, // "cloudkit.zoneshare"
zoneID: zone.zoneID
)
Other shares
For other shares I think you'll have to store the ID when accepting the share:
func windowScene(
_ windowScene: UIWindowScene,
userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata
) {
let isZoneWide = (metadata.share.recordID.recordName == CKRecordNameZoneWideShare)
if !isZoneWide {
// Store cloudKitShareMetadata.share.recordID somewhere!
}
let acceptSharesOperation = CKAcceptSharesOperation(
shareMetadatas: [cloudKitShareMetadata]
)
// ... Accept share code here
}
You might also be able to fetch the accepted shares, but I haven't tried this code:
let query = CKQuery(recordType: "cloudkit.share", predicate: NSPredicate(value: true))
let shares = try await container.sharedCloudDatabase.records(matching: query)
I have been working on an app were I try to implement the requestPaymentIntent. It was working first, but at some point it started doing the Bing search, and at some point just saying it found a mistake and doing nothing after that. This has been pointed out also in the topic: Payment domain intent is showing internet result instead of intent UI or intent confirmation using Sirikit.
One solution marked as good was provided there by William Hindenburg: "We have found that you need to add a paymentRecord to the INSendPaymentIntentResponse, before returning it from the confirm and handle methods in the Payment Intent Handler. Give this a shot and see if that fixes it for you."
I have tried to do this, but I can't figure it out. I have mainly problems with defining the status.
So in my handle method I first define the userActivity. Then I initialise the response object.
let response = INRequestPaymentIntentResponse (code: .success, userActivity: userActivity)
Then I want to add a paymentRecord to the response object:
response.paymentRecord = INPaymentRecord(payee: nil, payer: nil, currencyAmount: intent.currencyAmount, paymentMethod: nil, note: intent.note, status: ???)
Some parameters I do not need/use, so I filled in nil. Two things I would like to have in my app, the amount that needs to be paid, and for what this needs to be paid (the note). On the place of the ??? I have to fill in a status, I tried several things, but I can't figure it out. Since multiple people got this working already, can you please help me to explain how this works? Thanks a lot!
Apple really doesn't specify what status to use for confirm, but this works for me currently.
Confirm call:
response.paymentRecord = [self makePaymentRecordForIntent:intent withStatus:INPaymentStatusPending];
Send call:
intentResponse.paymentRecord = [self makePaymentRecordForIntent:intent withStatus:INPaymentStatusCompleted];
Genericized code:
-(INPaymentRecord *)makePaymentRecordForIntent:(INSendPaymentIntent *)intent withStatus:(INPaymentStatus)status {
INPaymentMethod *payMethod = [[INPaymentMethod alloc] initWithType:INPaymentMethodTypeChecking
name:#"Gold Star Checking"
identificationHint:#"1234"
icon:nil];
INPersonHandle *senderHandle = [[INPersonHandle alloc] initWithValue:#"first.last#example.com" type:INPersonHandleTypeEmailAddress];
NSPersonNameComponents *senderNameComp = [[NSPersonNameComponents alloc] init];
senderNameComp.givenName = #"First";
senderNameComp.familyName = #"Last";
INPerson *senderPerson = [[INPerson alloc] initWithPersonHandle:senderHandle
nameComponents:senderNameComp
displayName:#"First Last"
image:nil
contactIdentifier:nil
customIdentifier:nil];
INPaymentRecord *paymentRecord = [[INPaymentRecord alloc] initWithPayee:intent.payee
payer:senderPerson
currencyAmount:intent.currencyAmount
paymentMethod:payMethod
note:intent.note
status:status];
return paymentRecord;
}
I'm trying to write an app to show videos from a few of my channels in Vimeo. I am using VIMNetworking sdk provided by vimeo and I am using a pro account. I am first setting up my session and authentication in my AppDelegate:
let config = VIMSessionConfiguration()
config.clientKey = KEY
config.clientSecret = SECRET
config.scope = "public"
config.keychainService = SERVICE
VIMSession.setupWithConfiguration(config)
let acc = VIMAccountNew()
acc.accessToken = ACCESSTOKEN
acc.tokenType = "bearer"
acc.scope = "public"
VIMSession.sharedSession()?.changeAccount(acc)
and I am fetching videos from my channel like this:
let desc = VIMRequestDescriptor()
desc.urlPath = "/channels/1015923/videos"
desc.modelClass = VIMVideo.classForCoder()
desc.modelKeyPath = "data"
VIMSession.sharedSession()!.client.requestDescriptor(desc) { (resp, err) in
let v = resp?.result
debugPrint(v)
self.videos.insert((v as? NSArray)!, atIndex: 0)
}
So when I goto to access the video objects, I see the name and all the data associated with it except but the files property is nil. I understand I have to auth as my pro account to see the files, but I thought I was doing so? I need to see the files so that I can stream them to the player in the app.
This looks like it is a scope issue - you need to add the video_files scope in addition to public. See the list of all scopes here
I must have had a cached request or something. I am getting the video files now. Thanks for the help anyways!
I'd like to be able to determine which store the user connects to from inside my app, so that I can direct them to some appropriate content for their device AND store. Does anyone know how to get this information?
Basically, if the user is in the UK, and connects to the UK store, I want my function/method to return GB, if in Korea, I want KR, Australia = AU etc. Any help would be appreciated.
The approach of getting the country code of the user's locale will work ... but only if the user's iTunes store is the same as their locale. This won't always be the case.
If you create an in-app purchase item, you can use Apple's StoreKit APIs to find out the user's actual iTunes country even if it's different from their device locale. Here's some code that worked for me:
- (void) requestProductData
{
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers:
[NSSet setWithObject: PRODUCT_ID]];
request.delegate = self;
[request start];
}
- (void) productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *myProducts = response.products;
for (SKProduct* product in myProducts) {
NSLocale* storeLocale = product.priceLocale;
storeCountry = (NSString*)CFLocaleGetValue((CFLocaleRef)storeLocale, kCFLocaleCountryCode);
NSLog(#"Store Country = %#", storeCountry);
}
[request release];
// If product request didn't work, fallback to user's device locale
if (storeCountry == nil) {
CFLocaleRef userLocaleRef = CFLocaleCopyCurrent();
storeCountry = (NSString*)CFLocaleGetValue(userLocaleRef, kCFLocaleCountryCode);
}
// Now we're ready to start creating URLs for the itunes store
[super start];
}
Since iOS 13.0, Apple introduced the SKStorefront API.
It allows you to check the current AppStore country the user is connected to.
SKStorefront: An object containing the location and unique identifier of an Apple App Store storefront.
Overview
In-app products you create through App Store Connect are available for sale in every region with an App Store. You can use the storefront information to determine the customer's region, and offer in-app products suitable for that region. You must maintain your own list of product identifiers and the storefronts in which you want to make them available.
Topics
countryCode: The three-letter code representing the country associated with the App Store storefront.
identifier: A value defined by Apple that uniquely identifies an App Store storefront.
https://developer.apple.com/documentation/storekit/skstorefront
So far you can use 2 methods with their pros and cons:
StoreFront
->SDK >= 13
->The three-letter code representing the country associated with the App Store storefront
if let storeFront = SKPaymentQueue.default().storefront{
print("StoreFront CountryCode = ", storeFront.countryCode)
}
else {
print("StoreFront NOT Available")
}
OR
SKCloudServiceController
-> Available from iOS 9.3.
-> Requires permission. On tvOS it can become messy as I can't find a way to change the permissions in settings...
-> Permission text quite confusing.
let skCloudServiceController = SKCloudServiceController()
SKCloudServiceController.requestAuthorization { (status) in
guard status == .authorized else {
return
}
skCloudServiceController.requestStorefrontCountryCode(completionHandler: { (countryCode, error) in
if let error = error {
print("Failure: ", error)
}
else if let countryCode = countryCode {
print("Country code: ", countryCode)
}
})
}
Don't forget to include that in your .plist file:
<key>NSAppleMusicUsageDescription</key>
<string>Store information</string>
A hard way to get this function is to set up up a single app for every app store country. Each app holds it's own country store information. This assumes, that a user sticks to one store, which should be true for most people.
I suggest you try iTunes deep links. For example, http://itunes.com/apps/appname should take the user to the local App Store where she spends money.
I need the same functionality. At the moment I'm considering reading using data from NSLocale as default, but adding a setting in settings.app for user to customise this if it does not match.
This function is taken from an answer to another question of mine.
- (NSString *)getUserCountry
{
NSLocale *locale = [NSLocale currentLocale];
return [locale objectForKey: NSLocaleCountryCode];
}
You should probably use
[[userDefaults dictionaryRepresentation] objectForKey:#"NSLocaleCode"];
This will return a language code like en_US or en_UK or en_AU or even zh_CN, zh_MY, jp_JP and so on.
Parse the correct codes which you support and direct them accordingly.