How to accept/decline EKEvent invitation? - ios

I would like to allow my users to accept/decline a meeting invitation within my app.
I think what I need is to update somehow the EKParticipantStatus but it looks like it isn't possible to update.
Apple Docs: Event Kit cannot add participants to an event nor change participant
information
In this stackOverflow question someone suggested to bring the native EventKitUI, which I've tried like this:
class CalendarViewController: UIViewController, EKEventViewDelegate {
// .....
let eventController = EKEventViewController()
guard let eventWithIdentifier = MeetingsFetcher.eventStoreClass.event(withIdentifier: meeting.UUID) else {
return nil
}
eventController.delegate = self
eventController.event = eventWithIdentifier
eventController.allowsEditing = true
eventController.allowsCalendarPreview = true
let navCon = UINavigationController(rootViewController: eventController)
// customizing the toolbar where the accept/maybe/decline buttons appear
navCon.toolbar.isTranslucent = false
navCon.toolbar.tintColor = .blueGreen
navCon.toolbar.backgroundColor = .coolWhite10
// customizing the nav bar where the OK button appears
navCon.navigationBar.callinAppearence()
present(navCon, animated: true, completion: nil)
// .....
// view gets dismissed, so it does detects the action, but no effect
func eventViewController(_ controller: EKEventViewController, didCompleteWith action: EKEventViewAction) {
controller.dismiss(animated: true, completion: nil)
}
The native UI shows pretty good but the buttons don't make any effect in the native calendar.
Am I missing something or it's just not possible? Why would they allow to interact with those buttons if it's not possible to save anything?
Thank you!
PD: I have these permissions in the info.plist:
Privacy - Calendars Usage Description
Privacy - Contacts Usage Description
Privacy - Reminders Usage Description
UPDATE:
Please note that EKEventEditViewController is not what I am looking for. This screen wouldn't allow me to accept or decline the event, it would only allow me to edit the details.

To allow the user to create, edit, or delete events, use the EKEventEditViewDelegate protocol.
let eventController = EKEventViewController()
guard let eventWithIdentifier = MeetingsFetcher.eventStoreClass.event(withIdentifier: meeting.UUID) else {
return nil
}
eventController.delegate = self
eventController.event = eventWithIdentifier
eventController.editViewDelegate = self
...
CalendarViewController class must conform to the EKEventEditViewDelegate protocol and must implement the eventEditViewController method to dismiss the modal view controller as shown below:
func eventEditViewController(_ controller: EKEventEditViewController,
didCompleteWith action: EKEventEditViewAction) {
switch (action) {
case EKEventEditViewActionCanceled:
case EKEventEditViewActionSaved:
...
}
}

Related

How to make a button modify another button functions

I have two UIViewController. In the first one, I have a button that adds some views, one at a time, to the main view. In the second one, I set up a store, so that when I press on a button I unlock some features of my app. Now, I perfectly know (I hope) how to handle the part where I make the VCs comunicate and I trigger some other easy functions, what I don't know is how to make the store button increase the button's functions.
WHAT I NEED:
Right now the button adds a maximum of 10 views (complete version). I want that before the user buys my app, he gets to add a maximum of 3 views and then, when he buys it, the function I already have (the one to add 10 views)starts to work and replaces the other one.
MAIN VIEW CONTROLLER
var messageArray = [UIView] ()
I attached all of my UIView from the storyboard and I appended them to my array in the viewDid load like this: messageArray.append(View1)
#IBAction func addMessageViewButton(_ sender: Any) {
let hiddenViews = messageArray.filter { $0.isHidden }
guard !hiddenViews.isEmpty else {
let sheet = UIAlertController(title: "max reached", message: nil, preferredStyle: .actionSheet)
let ok = UIAlertAction(title: "OK", style: .cancel, handler: nil)
let closeAll = UIAlertAction(title: "Close all", style: .destructive) { (addMessage) in
view1.isHidden = true
view2.isHidden = true
view3.isHidden = true
view4.isHidden = true
view5.isHidden = true
view6.isHidden = true
view7.isHidden = true
view8.isHidden = true
view9.isHidden = true
view10.isHidden = true
}
sheet.addAction(ok)
sheet.addAction(closeAll)
present(sheet, animated: true, completion: nil)
return
}
let randomHiddenView = hiddenViews.randomElement()
randomHiddenView?.isHidden = false
}
SHOP VIEW CONTROLLER
Here I won't post all of the code because it would be too much and of course unnecessary, since the important thing to know here is that there's a button and if the user presses it and he proceeds with the purchase, he will get the function I posted up here working instead of the one that allows him to have just 3 views.
func unlock() {
let appdelegate = UIApplication.shared.delegate
as! AppDelegate
appdelegate.viewController!.functionToHave10Views()
//viewControlled is declared in the app delegate like this ` var viewController: ViewController?`
//I know I don't physically have `functionToHave10Views()`, but I guess I'll turn the code of my button into a function, so just to be clear, I'm referring to that function.
buyButton.isEnabled = false
}
In your main view controller:
var isLocked = true
#IBAction func addMessageViewButton(_ sender: Any) {
if isLocked {
// Do something for when is locked
} else {
// Do something for when is unlocked
}
}
Then in your shop view controller:
func unlock() {
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.viewController!.isLocked = false
buyButton.isEnabled = false
}

Can I use the View CNContactViewController without saving the contact to the contactStore?

I searched now for at least 2 hours and couldn't find an answer for this.
I need to have an object of type CNContact. I found the CNContactViewController and this view would meet perfectly my requirements. With it I can create a new contact with the predefined view and this gives me an CNContact object.
This is my problem:
The CNContactViewController saves the created contact to the contactStore. But I don't want this. Can I suppress this behaviour? I'm pretty sure there must be a solution.
This is my code for creation of the ViewController:
let contactController = CNContactViewController(forNewContact: nil)
contactController.allowsEditing = true
contactController.allowsActions = false
contactController.displayedPropertyKeys = [CNContactPostalAddressesKey, CNContactPhoneNumbersKey, CNContactGivenNameKey]
contactController.delegate = self
contactController.view.layoutIfNeeded()
let navigationController = UINavigationController(rootViewController: contactController)
self.present(navigationController, animated: true)
Here I would like to use the contact without having it in the Addressbook:
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
viewController.navigationController?.dismiss(animated: true)
}
Thanks for your help!
But I don't want this. Can I suppress this behavior? I'm pretty sure there must be a solution.
There isn’t. If the user saves the edited contact (taps Done) then by the time you hear about it, it has been saved into the contacts database and there is nothing you can do about it.
Of course you can turn right around and delete the saved contact. But you cannot prevent the saving from taking place to begin with.
I'm doing something like this:
let contact = CNMutableContact()
contact.contactType = .person
contact.givenName = "giveName"
let cvc = CNContactViewController(forUnknownContact: contact)
cvc.delegate = self
cvc.contactStore = CNContactStore()
cvc.allowsEditing = false
self.navigationController?.pushViewController(cvc, animated: true)
You can allow the user to save the contact to the internal contact store - but it is not stored automatically.
Remember to implement the delegate functions (fx as an extension)
extension MyContactsViewController: CNContactViewControllerDelegate {
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
viewController.dismiss(animated: true, completion: nil)
}
func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool {
print(property.key)
return true
}
}

CNContactViewController setEditing true before appear

I have a task to show the contact editing screen at once during his appearance (such as WhatsApp), I show him the following way.
#objc private func presentContactEditController() {
guard var contact = contactModel.contact else { return }
if !contact.areKeysAvailable([CNContactViewController.descriptorForRequiredKeys()]) {
do {
let contactStore = CNContactStore()
contact = try contactStore.unifiedContact(withIdentifier: contact.identifier, keysToFetch: [CNContactViewController.descriptorForRequiredKeys()])
} catch {
debugPrint("presentContactEditController error", error.localizedDescription)
}
}
let cnContactViewController = CNContactViewController(for: contact)
cnContactViewController.delegate = self
cnContactViewController.setEditing(true, animated: false)
let contactNaviController = UINavigationController(rootViewController: cnContactViewController)
present(contactNaviController, animated: true, completion: nil)
}
But there is a screen with information about this contact. So I tried to do it through the heir of CNContactViewController, different methods ViewController life cycle, but it works only in viewDidAppear method, but it will be visible to the user. How can I solve this problem? Thank you.
just change let cnContactViewController = CNContactViewController(for: contact)
to
let cvc = CNContactViewController(forNewContact: contact)
it will work for you
I came to the conclusion that that WhatsApp create a custom screen for this purpose. Just I saw the same screen in Telegram only with a modified design.

CNContactViewController Cancel Button Not Working

I'm trying to use the built-in new contact UI and am getting unexpected behavior with the cancel button. The code below works and calls up the new contact screen but the cancel button will only clear the screen entries not cancel out of the new contact screen. In the built in contacts app hitting cancel returns to the contact list screen. I would like the cancel button to close out the window.
#IBAction func newTwo(sender: AnyObject) {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let npvc = CNContactViewController(forNewContact: nil)
npvc.delegate = self
self.navigationController?.pushViewController(npvc, animated: true)
}
}
}
did you implement CNContactViewControllerDelegate methods?
Here's a link to documentation
for example:
func contactViewController(viewController: CNContactViewController, didCompleteWithContact contact: CNContact?) {
self.dismissViewControllerAnimated(true, completion: nil)
}
It worked for me using the following code:
Swift 3
func contactViewController(_ vc: CNContactViewController, didCompleteWith con: CNContact?) {
vc.dismiss(animated: true)
}
Also I changed the way I was calling the controller:
Instead of:
self.navigationController?.pushViewController(contactViewController, animated: true)
the solution was:
self.present(UINavigationController(rootViewController: contactViewController), animated:true)
I found the solution using the example code Programming-iOS-Book-Examples written by Matt Neuburg:
Better way to do the dismissing would be to check if the contact is nil and then dismiss it. The dismiss doesn't work if you've pushed the view controller from a navigation controller. You might have to do the following:
func contactViewController(viewController: CNContactViewController, didCompleteWithContact contact: CNContact?) {
if let contactCreated = contact
{
}
else
{
_ = self.navigationController?.popViewController(animated: true)
}
}

Present GameCenter authenticationVC again

I'm facing a little issue here and I hope someone will help me figure out what is wrong.
*The test project presented below can be find here : http://goo.gl/wz84aA (FR) or https://goo.gl/0m8LrZ (Mega.NZ) *
I'm trying to present to the user the authentification view controller proposed by apple for the GameCenter feature. More precisely, re-present it if he canceled it on the first time.
I have a game with a storyboard like that :
GameNavigationController :
class GameNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("showAuthenticationViewController"), name: PresentAuthenticationViewController, object: nil)
GameKitHelper.sharedInstance.authenticateLocalPlayer()
}
func showAuthenticationViewController() {
let gameKitHelper = GameKitHelper.sharedInstance
if let authenticationViewController = gameKitHelper.authenticationViewController {
self.topViewController.presentViewController(authenticationViewController, animated: true, completion: nil)
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
MenuViewController :
class MenuViewController: UIViewController {
#IBAction func didTapLeaderboardBTN() {
// TRY 2
//if ( !GameKitHelper.sharedInstance.gameCenterEnabled) {
GameKitHelper.sharedInstance.authenticateLocalPlayer()
//} else {
GameKitHelper.sharedInstance.showGKGameCenterViewController(self)
//}
}
}
GameKitHelper :
import GameKit
import Foundation
let PresentAuthenticationViewController = "PresentAuthenticationViewController"
let singleton = GameKitHelper()
class GameKitHelper: NSObject, GKGameCenterControllerDelegate {
var authenticationViewController: UIViewController?
var lastError: NSError?
var gameCenterEnabled: Bool
class var sharedInstance: GameKitHelper {
return singleton
}
override init() {
gameCenterEnabled = true
super.init()
}
func authenticateLocalPlayer () {
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = { (viewController, error) in
self.lastError = error
if viewController != nil {
self.authenticationViewController = viewController
NSNotificationCenter.defaultCenter().postNotificationName(PresentAuthenticationViewController, object: self)
} else if localPlayer.authenticated {
self.gameCenterEnabled = true
} else {
self.gameCenterEnabled = false
}
}
}
func showGKGameCenterViewController(viewController: UIViewController!) {
if ( !self.gameCenterEnabled ) {
println("Local player is not authenticated")
// TRY 1
//self.authenticateLocalPlayer()
return
}
let gameCenterViewController = GKGameCenterViewController()
gameCenterViewController.gameCenterDelegate = self
gameCenterViewController.viewState = .Leaderboards
viewController.presentViewController(gameCenterViewController, animated: true, completion: nil)
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!) {
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
}
What is currently working :
if the user is previously logged-in (within the GameCenter app) then he's able to open the leaderboard view ;
if the user wasn't logged-in then he's prompted to log-in when the game navigation controller is loaded (and then open the leaderboard).
What is NOT currently working :
if he cancel three time the authentification, then the authentification won't appear anymore (even at launch) ; // Apparently a known "problem", not "fixable"
if the user cancel his authentification, when he tries to load the leaderboard the authentification won't appear again.
I tried 2-3 things as you can see in the commented code above, but none of them is working ; I can't make the authentification view appear again.
PS : My code is written in Swift, but help in Objective-C is welcomed as well.
As you have found out, if the Game Center authentication dialog is canceled 3 times, then you can't bring it back without resetting the device.
There is another "security feature" built into Game Center which does not allow an app to re-authenticate if the user has already canceled the dialog without leaving the app. So for your authentication dialog to show, the user must leave and then re-enter your app.
There is really no way around it. What I've done in a couple of projects is to display a message to the user:
Game Center not available. Please make sure you are signed in through the Game Center app
I will show that message after trying to authenticate and if Game Center isn't available or the user is not signed in.
If you want to be able to re-present this to your user then go to settings -> General -> Reset -> -> Reset Location & Privacy.
This will force iOS to forget preferences for apps for example whether they can use location services, send you push notifications and also game centre preferences. Keep in mind this will reset privacy settings for all apps.

Resources