Using SKStoreReviewController for inAppReview takes time until the prompt appears, is there any way to make it show faster ?
Also, submit button is always dimmed, not allowing me to rate, is this because i didn't upload app to appstore yet?
import StoreKit
protocol InAppReviewProtocol {
func requestInAppReview()
}
extension InAppReviewProtocol {
func requestInAppReview() {
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
} else {
// Fallback on earlier versions
if let appStoreLink = URL(string: Constants.shareApp.url) {
UIApplication.shared.openURL(appStoreLink)
}
}
}
}
No, you can not make is faster, the system decides when to show the alert.
Read apple documentation on SKStoreReviewController.requestReview() for more details.
The submit button is disabled as long as you run your app via XCode to prevent you from giving yourself lots of 5-star votings ;)
See below from the apple docs of requestReview method:-
Although you should call this method when it makes sense in the user
experience flow of your app, the actual display of a rating/review
request view is governed by App Store policy. Because this method may or may not present an alert, it's not appropriate to call it in response to a button tap or other user action.
For more details,
go to
this link
So you got the answer for your first question. Your second question is just simple, in development mode you cannot give rating as it makes sense also. You have to publish your app to appStore first and download app from there and can give a review.
Hope it helps you..
Related
This question already has answers here:
Why does viewWillAppear not get called when an app comes back from the background?
(7 answers)
Closed 4 years ago.
I am working on project that needs to load the users contacts. For this I need the contacts information. Thus I am using Contacts Framework. It is easy to use and really very fast also. I am new to iOS so I just used the code snippet of getting contacts information.
What I have done: But I have problem, and that is when the user install the application and go the respective ViewController, the ViewController shows Dialog for permission. There user can Deny and Allow the permission. My app works well when user allows the permission but does not work in other way. So I used a function to check if user has given my app the permission or not.
So I read that when user has not granted the permission we can not do anything. Except we can take him to settings where he can allow the permission and get back the app. here is the code I am using to go to the settings app.
if let appSettings = URL(string: UIApplicationOpenSettingsURLString + Bundle.main.bundleIdentifier!) {
if UIApplication.shared.canOpenURL(appSettings) {
UIApplication.shared.open(appSettings)
}
}
Problem:
now my problem is critical, and that is I know I can take user to
settings view, now what if user still do not allow the Permission and
just get back to our App, in this case how to check if user has given
us permission or not??
I am new to iOS and swift so please help me through example. I have searched a lot but did not find anything. In Android there are callback and also onResume could be used, but in iOS I used ViewWillAppear thinking as equivalent of onResume but that did not work.
If you had a refresh button you could do something like the following:
#IBAction func refreshButtonPressed(_ sender: UIButton) {
ContactsService.sharedInstance.CNContactStore.requestAccess(to: .contacts, completion: { granted, error in
if !granted {
//TODO: - Do something (e.g)
self.[method to display an access denied alert to the user]
return
} else {
[insert your code here]
}
}
Also, as another user has stated in a reply to your original question: Why does viewWillAppear not get called when an app comes back from the background? may be of interest to you. You may want to call the contents of the ContactsService.sharedInstance.CNContactStore.requestAccess( .... { } block inside a function and hook that up to a listener for the UIApplication.willEnterForegroundNotification
https://developer.apple.com/documentation/contacts
https://developer.apple.com/documentation/contacts/cncontactstore
https://developer.apple.com/documentation/contacts/cncontactstore/1402873-requestaccess
I tested:
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
which is for putting app on background and it works.
How do I put app back on foreground?
I tried:
UIControl().sendAction(#selector(URLSessionTask.resume), to: UIApplication.shared, for: nil)
But eventually it crashes...
Thank you
Update:
Since you've indicated that you're looking for any technical solution, even those not compatible with the App Store or Apple's terms, this should be possible using the Private API LSApplicationWorkspace: openApplicationWithBundleID. Try something like this:
Create a .h file and set up an interface to the LSApplicationWorkspace class and list the required method. You will need to #import "PrivateHeaders.h" in your bridging header.
//
// PrivateHeaders.h
//
#ifndef PrivateHeaders_h
#define PrivateHeaders_h
#interface LSApplicationWorkspace : NSObject
- (bool)openApplicationWithBundleID:(id)arg1;
#end
#endif /* PrivateHeaders_h */
You should then be able to call this function and pass in the Bundle Identifier of your app as an string.
//
// SomeClass.swift
//
import MobileCoreServices
let workspace = LSApplicationWorkspace()
/**
Launch an App given its bundle identifier
- parameter bundleIdentifier: The bundle identifier of the app to launch
- returns: True if app is launched, otherwise false
*/
func openApp(withBundleIdentifier bundleIdentifier: String) -> Bool {
// Call the Private API LSApplicationWorkspace method
return workspace.openApplication(withBundleID: bundleIdentifier)
}
Original:
What you are doing is likely a violation of the iOS Human Interface Guidelines (although the "Don’t Quit Programmatically" is no longer specifically defined), so as the comments have said, it is not suited to the App Store. Regardless, once your app is suspended in this way, I don't expect that there is a way to resume it programmatically, unless you can hook into a Background Operation to run URLSessionTask.resume, but I have not tested it and am unsure whether it can work.
Apps can be launched (and hence brought into the foreground) programmatically from another app or today extension by using a Custom URL Scheme, or via a Push Notification. It isn't possible to launch the app from the Background Operation via a URL Scheme, since it is part of the UIKit framework, which must be run in the main thread.
In summary, I think your best option is to try to use a Notification. This just means that the user will need to click on the notification to bring your app back into the foreground.
Closing/opening the app should be done explicitly by the user. Any other way of closing or opening the app is not supported by Apple and will be rejected when uploaded to app store. iOS Human Interface Guideline states:
Don’t Quit Programmatically
Never quit an iOS application
programmatically because people tend to interpret this as a crash.
However, if external circumstances prevent your application from
functioning as intended, you need to tell your users about the
situation and explain what they can do about it. Depending on how
severe the application malfunction is, you have two choices.
*Display
an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures usersthat
there’s nothing wrong with your application. It puts usersin control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
*If only some of your application's features are
not working, display either a screen or an alert when people activate
the feature. Display the alert only when people try to accessthe
feature that isn’t functioning
Just as a follow up to Jordan's excellent answer I want to give an explanation for why your code works in the first place and why that alone will get your app rejected, even without any functionality to make it active again and bring it to the foreground.
As maddy pointed out in a comment, you're basically calling a method from UIApplication's private API. This works due to the Objective-C runtime's dynamic linking. You might wonder "But I am using Swift, what does that have to do with Objective-C?" The answer lies in #selector mechanism. A Selector is basically just a symbol that the Objective-C runtime looks up in a table to get a method it invokes (for you). This is why it's technically not correct to say you "call a method" when you do something like myObjectInstance.someMethod(). The correct way to phrase that would be to "send a message" to the object, because that's what is happening in the runtime. The target-action mechanism is build around that. The sendAction(_: Selector?, to: Any?) method does the same thing. So in effect your code does the following:
Get the symbol that corresponds to URLSessionTask's suspend() method.
Tell the shared instance of UIApplication to invoke the method that it has for that symbol.
Now usually that would result in a crash with the typical "unknown selector sent to instance..." error message. But here, by sure coincidence UIApplication also has a method for that instance (or rather, the runtime also has one of its methods listed in its table for that symbol). You kind of "found" a method that is not declared in its public header. You successfully circumvented a compile-time check for this and invoke a method that is part of a private API. This is explicitly forbidden in the Apple Developer Program License Agreement
Besides all that, I would strongly advise against trying to design an app that way in the first place. As maddy pointed out it's also likely considered to violate the HIGs. Even if you're not trying to do anything malicious and properly explain the feature in your app's description, that won't make Apple let it slide (I assume). Personally, as a user, I'd also find it annoying if the app did something the system already has a specific mechanic for in a different manner, at least in terms of app's coming to background and foreground.
I don't think it can be done without user interaction
The option is you can generate a push notification to tell the user to bring the application to foreground
When the operating system delivers push notification and the target application is not running in the foreground, it presents the notification.
If there is a notification alert and the user taps or clicks the action button (or moves the action slider), the application launches and calls a method to pass in the local-notification object or remote-notification payload.
I'm implementing Siri Shortcut. My objective is add shortcuts of functions to SIRI SHORTCUT on Settings app.
In order to do so, I need to first register NSUserActivity & then handle the shortcuts calls from app delegate.
The problem is I'm not sure where and how to register the activity properly from a tutorail I watched.
Where should I register?
According to the tutorial I added this code on a view controller.
Since self.userActivity is accessible from app delegete I'm wondering if I could add the code below on app delegate.
func registerShortcut() {
if #available(iOS 12.0, *) {
let activity = NSUserActivity(activityType: "jp.co.mycompany.MyApp.openCamera")
activity.title = "Camera Shortcut"
activity.isEligibleForSearch = true
activity.isEligibleForPrediction = true
self.userActivity = activity
self.userActivity?.becomeCurrent()
}
}
How many times should I register
Without controlling how many times the registration code is called, app will call the registration code
everytime app is launched. Is calling the registration multiple times causes any problem?
There are many ways to implement Shortcuts and Siri Suggestions in your app, so long as they follow the Human interface Guidelines set by apple. you should't be to concerned about implementation. I believe you would like to register for a action donation to Siri Shortcuts.
Take a dive into Apple's Example Code for programming Siri Shortcuts. I believe this the best resource on the matter. This code is very detailed and well documented.
"How many times should I register?"
"Where should I register?"
You should register your donation every time the user performs the action:
Donating the intent each time the user performs an action helps Siri
learn about the user’s behavior, which helps Siri better predicate
when the user may want to perform that action again.
So in your case, I wouldn't register the donation in the AppDelegate, instead I'd do it when a button is pressed or the user does some other action like opening the camera. Most often I see developer's group actions into a single file to manage them better.. Then call them when their respective actions are activated by the user.
I tested:
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
which is for putting app on background and it works.
How do I put app back on foreground?
I tried:
UIControl().sendAction(#selector(URLSessionTask.resume), to: UIApplication.shared, for: nil)
But eventually it crashes...
Thank you
Update:
Since you've indicated that you're looking for any technical solution, even those not compatible with the App Store or Apple's terms, this should be possible using the Private API LSApplicationWorkspace: openApplicationWithBundleID. Try something like this:
Create a .h file and set up an interface to the LSApplicationWorkspace class and list the required method. You will need to #import "PrivateHeaders.h" in your bridging header.
//
// PrivateHeaders.h
//
#ifndef PrivateHeaders_h
#define PrivateHeaders_h
#interface LSApplicationWorkspace : NSObject
- (bool)openApplicationWithBundleID:(id)arg1;
#end
#endif /* PrivateHeaders_h */
You should then be able to call this function and pass in the Bundle Identifier of your app as an string.
//
// SomeClass.swift
//
import MobileCoreServices
let workspace = LSApplicationWorkspace()
/**
Launch an App given its bundle identifier
- parameter bundleIdentifier: The bundle identifier of the app to launch
- returns: True if app is launched, otherwise false
*/
func openApp(withBundleIdentifier bundleIdentifier: String) -> Bool {
// Call the Private API LSApplicationWorkspace method
return workspace.openApplication(withBundleID: bundleIdentifier)
}
Original:
What you are doing is likely a violation of the iOS Human Interface Guidelines (although the "Don’t Quit Programmatically" is no longer specifically defined), so as the comments have said, it is not suited to the App Store. Regardless, once your app is suspended in this way, I don't expect that there is a way to resume it programmatically, unless you can hook into a Background Operation to run URLSessionTask.resume, but I have not tested it and am unsure whether it can work.
Apps can be launched (and hence brought into the foreground) programmatically from another app or today extension by using a Custom URL Scheme, or via a Push Notification. It isn't possible to launch the app from the Background Operation via a URL Scheme, since it is part of the UIKit framework, which must be run in the main thread.
In summary, I think your best option is to try to use a Notification. This just means that the user will need to click on the notification to bring your app back into the foreground.
Closing/opening the app should be done explicitly by the user. Any other way of closing or opening the app is not supported by Apple and will be rejected when uploaded to app store. iOS Human Interface Guideline states:
Don’t Quit Programmatically
Never quit an iOS application
programmatically because people tend to interpret this as a crash.
However, if external circumstances prevent your application from
functioning as intended, you need to tell your users about the
situation and explain what they can do about it. Depending on how
severe the application malfunction is, you have two choices.
*Display
an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures usersthat
there’s nothing wrong with your application. It puts usersin control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
*If only some of your application's features are
not working, display either a screen or an alert when people activate
the feature. Display the alert only when people try to accessthe
feature that isn’t functioning
Just as a follow up to Jordan's excellent answer I want to give an explanation for why your code works in the first place and why that alone will get your app rejected, even without any functionality to make it active again and bring it to the foreground.
As maddy pointed out in a comment, you're basically calling a method from UIApplication's private API. This works due to the Objective-C runtime's dynamic linking. You might wonder "But I am using Swift, what does that have to do with Objective-C?" The answer lies in #selector mechanism. A Selector is basically just a symbol that the Objective-C runtime looks up in a table to get a method it invokes (for you). This is why it's technically not correct to say you "call a method" when you do something like myObjectInstance.someMethod(). The correct way to phrase that would be to "send a message" to the object, because that's what is happening in the runtime. The target-action mechanism is build around that. The sendAction(_: Selector?, to: Any?) method does the same thing. So in effect your code does the following:
Get the symbol that corresponds to URLSessionTask's suspend() method.
Tell the shared instance of UIApplication to invoke the method that it has for that symbol.
Now usually that would result in a crash with the typical "unknown selector sent to instance..." error message. But here, by sure coincidence UIApplication also has a method for that instance (or rather, the runtime also has one of its methods listed in its table for that symbol). You kind of "found" a method that is not declared in its public header. You successfully circumvented a compile-time check for this and invoke a method that is part of a private API. This is explicitly forbidden in the Apple Developer Program License Agreement
Besides all that, I would strongly advise against trying to design an app that way in the first place. As maddy pointed out it's also likely considered to violate the HIGs. Even if you're not trying to do anything malicious and properly explain the feature in your app's description, that won't make Apple let it slide (I assume). Personally, as a user, I'd also find it annoying if the app did something the system already has a specific mechanic for in a different manner, at least in terms of app's coming to background and foreground.
I don't think it can be done without user interaction
The option is you can generate a push notification to tell the user to bring the application to foreground
When the operating system delivers push notification and the target application is not running in the foreground, it presents the notification.
If there is a notification alert and the user taps or clicks the action button (or moves the action slider), the application launches and calls a method to pass in the local-notification object or remote-notification payload.
This is my code for Requesting Review :
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
}
else{
print("Review is not available with in the app")
}
In Development Mode it is working properly & I am able to get PopUp like this:
But In Live app downloaded from appstore, App isnot showing this ratings Popup and nothing happens if user taps out on Ratings Button.
From the documentation:
Although you should call this method when it makes sense in the user experience flow of your app, the actual display of a rating/review request view is governed by App Store policy. Because this method may or may not present an alert, it's not appropriate to call it in response to a button tap or other user action.
(Highlight mine)
If you have a Ratings Button like you said in your question, you should not expect it to show the prompt.
The prompt will only show up if:
The user hasn't disabled Review Prompts in Settings.
The prompt has been shown to the user 3 times or less in a year.
If you must request a review upon user interaction, you must direct your users to the App Store page of your app instead, using code like this (taken from Requesting App Store Reviews Sample Code):
#IBAction func requestReviewManually() {
// Note: Replace the XXXXXXXXXX below with the App Store ID for your app
// You can find the App Store ID in your app's product URL
guard let writeReviewURL = URL(string: "https://itunes.apple.com/app/idXXXXXXXXXX?action=write-review")
else { fatalError("Expected a valid URL") }
UIApplication.shared.open(writeReviewURL, options: [:], completionHandler: nil)
}