I want to get localised in app purchase price with currency symbol and display it on UILabel. But I don't know how to do that since I don't know how to get SKProduct object. Maybe it's possible to get it using apple product id?
extension SKProduct {
/// - returns: The cost of the product formatted in the local currency.
var regularPrice: String? {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = self.priceLocale
return formatter.string(from: self.price)
}}
EDIT:
let productIds: [String] = ["id1", "id2"]
getProducts(productIdentifiers: productIds)
SKProduct objects are returned as part of an SKProductsResponse
object. (c) Apple documentation
How to fetching product information: Fetching Product Information from the App Store
Also take a look to useful video from WWDC: Using Store Kit for In-App Purchases with Swift 3
Summarizing above documentation:
class ProductResolver: NSObject, SKProductsRequestDelegate {
// Keep a strong reference to the product request.
var request: SKProductsRequest!
func getProducts(productIdentifiers: [String]) {
let productIdentifiers = Set(productIdentifiers)
request = SKProductsRequest(productIdentifiers: productIdentifiers)
request.delegate = self
request.start()
}
// SKProductsRequestDelegate protocol method.
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
for product in response.products {
print("Local currency \(product.regularPrice)")
}
}
}
Related
I'm trying to understand implementing IAP using Xcode 12 and SwiftUI. I prepared everything using App Store Connect and also registered the product id.
I'm using a StoreManager class adapting the SKProductsRequestDelegate protocol to fetch the corresponding SKProduct of the specified product id.
However, the func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) never gets called even when I'm working with a physical device. Actually, the console prints nothing at all.
I don't understand why this function never gets called.
class StoreManager: NSObject, SKProductsRequestDelegate {
override init() {
super.init()
getProducts()
}
func getProducts() {
let productID = "com.dev8882.MyDungeon.IAP.PowerSword"
let request = SKProductsRequest(productIdentifiers: Set([productID]))
request.delegate = self
request.start()
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("Did receive response")
}
}
My code is a bit different. It's been about 3 years, so I'm not able to be as detailed in my explanation of why this works, but basically I'm creating my product ID(s) as an NSSet, then force-casting it as a Set of Strings:
let productID = NSSet(objects: "com.dev8882.MyDungeon.IAP.PowerSword")
let request = SKProductsRequest(productIdentifiers: productID as! Set<String>)
I am not sure why this fixed my problem:
According the Apple docs a strong reference SKProductsRequest should by used to start the request. So I updated my code like this:
class StoreManager: NSObject, SKProductsRequestDelegate {
var request: SKProductsRequest!
override init() {
super.init()
request = SKProductsRequest()
self.getProducts()
}
func getProducts() {
print("OK")
let productID = "com.dev8882.MyDungeon.IAP.PowerSword"
let request = SKProductsRequest(productIdentifiers: Set([productID]))
request.delegate = self
request.start()
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("Did receive response")
}
}
It works fine now.
I am building a purchase order app and I have a table view that shows the current purchase order. I would like to be able to send an email with the data of that purchase order to the supplier.
I have everything working fine but I am unable to set the message body to data from firebase.
My firebase database looks like this and I would like to extract the name, info, quantity and price of each items in the list and populate the email with this data.
and this is how I am saving the data
func saveItemInBackground(shoppingItem: ShoppingItem, completion: #escaping (_ error: Error?) -> Void) {
let ref = firebase.child(kSHOPPINGITEM).child(shoppingItem.shoppingListId).childByAutoId()
shoppingItem.shoppingItemId = ref.key
ref.setValue(dictionaryFromItem(item: shoppingItem)) { (error, ref) -> Void in
completion(error)
}
}
I am using this code to present the email.
func configureMailController() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self
firebase.child(kSUPPLIEREMAIL).child(FUser.currentId()).child("SupplierEmail").observe(.value, with: {
snapshot in
if snapshot.exists() {
let toEmail = snapshot.value
mailComposerVC.setToRecipients(["\(toEmail as! String)"])
mailComposerVC.setSubject("New EDM Pro Purchase Order")
mailComposerVC.setMessageBody("", isHTML: false)
} else {
self.showMailError()
}
})
return mailComposerVC
}
and was wondering if i could user a function to set the email body and then call the function in the line mailComposerVC.setMessageBody("", isHTML: false)
Alternatively if this is not possible I could create a pdf of the tableview and attach it to the email. I am not sure how this could be done.
Any help on this would be amazing!
Answer is below, image is here:
I was searching how to do this for a couple of days and was only able to find people who stored UILocalNotificaations in NSUserDefaults. Saving these in NSUserDefaults seemed wrong to me because it is supposed to be used for small flags. I just now finally figured out how to store notifications in CoreData. This is Using Xcode 7.3.1 and Swift 2.2
First off you need to create a new entity in your CoreDataModel
and then add a single attribute to it. the attribute should be of type Binary Data I named my table/entity "ManagedFiredNotifications" and my attribute "notification". it should look like this:
Image linked in Question above.
Next you need to add an extension to UILocalNotification it should go like this:
extension UILocalNotification {
func save() -> Bool {
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
let firedNotificationEntity = NSEntityDescription.insertNewObjectForEntityForName("ManagedFiredNotifications", inManagedObjectContext: appDelegate!.managedObjectContext)
guard appDelegate != nil else {
return false
}
let data = NSKeyedArchiver.archivedDataWithRootObject(self)
firedNotificationEntity.setValue(data, forKey: "notification")
do {
try appDelegate!.managedObjectContext.save()
return true
} catch {
return false
}
}
}
Now for saving a notification all you need to do is call
UILocalNotification.save()
On the notification you would like to save. my notifications were named 'notification' so I would call notification.save()
To retrieve a notification you need a method like this
func getLocalFiredNotifications() -> [UILocalNotification]? {
let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)!.managedObjectContext
let firedNotificationFetchRequest = NSFetchRequest(entityName: "ManagedFiredNotifications")
firedNotificationFetchRequest.includesPendingChanges = false
do {
let fetchedFiredNotifications = try managedObjectContext.executeFetchRequest(firedNotificationFetchRequest)
guard fetchedFiredNotifications.count > 0 else {
return nil
}
var firedNotificationsToReturn = [UILocalNotification]()
for managedFiredNotification in fetchedFiredNotifications {
let notificationData = managedFiredNotification.valueForKey("notification") as! NSData
let notificationToAdd = NSKeyedUnarchiver.unarchiveObjectWithData(notificationData) as! UILocalNotification
firedNotificationsToReturn.append(notificationToAdd)
}
return firedNotificationsToReturn
} catch {
return nil
}
}
Note that this returns an array of UILocalNotifications.
When retrieving these if you plan on removing a few of them and then storing the list again you should remove them when you get them something like this works:
func loadFiredNotifications() {
let notifications = StudyHelper().getLocalFiredNotifications()
if notifications != nil {
firedNotifications = notifications!
} else {
// throw an error or log it
}
classThatRemoveMethodIsIn().removeFiredLocalNotifications()
}
I hope this helps someone who had the same problems that I did trying to implement this.
I'm developing an app and using Stripe SDK and Card.io SDK. What i want to happen is populate the STPPaymentCardTextField Card Number,Expiry Month and Year with Card.io scanned credit card value. I Tried:
var paymentField = STPPaymentCardTextField()
func userDidProvideCreditCardInfo(cardInfo: CardIOCreditCardInfo!, inPaymentViewController paymentViewController: CardIOPaymentViewController!) {
var scanViewController: CardIOPaymentViewController = CardIOPaymentViewController(paymentDelegate: self)
paymentField.cardNumber = cardInfo.cardNumber
paymentField.expirationMonth = cardInfo.expiryMonth
paymentField.expirationYear = cardInfo.expiryYear
paymentViewController.dismissViewControllerAnimated(true, completion: nil)
}
I'm having an error Cannot assign to the result of this expression for each paymentField append.
What do you think i can do with this? Thanks!
STPPaymentCardTextField fields are read-only and you can only "get" from those properties.
STPPaymentCardTextField is used to collect credit card details. In your case you are already doing that using CardIOCreditCardInfo. Once you have the credit card details you can assemble the data into an STPCardParams object.
Once you've collected the card number, expiration, and CVC, package them up in an STPCardParams object and invoke the createTokenWithCard: method on the STPAPIClient class.
Now your method may look like this...
func userDidProvideCreditCardInfo(cardInfo: CardIOCreditCardInfo!, inPaymentViewController paymentViewController: CardIOPaymentViewController!) {
let card: STPCardParams = STPCardParams()
card.number = info.cardNumber
card.expMonth = info.expiryMonth
card.expYear = info.expiryYear
card.cvc = info.cvv
STPAPIClient.sharedClient().createTokenWithCard(card, completion: {(result, error) -> Void in
if error == nil {
// Either save the card token with your customer data or charge them right away
//createBackendChargeWithToken(token)
}
else {
//handleError(error)
}
})
}
paymentViewController?.dismissViewControllerAnimated(true, completion: nil)
}
Update May be the right answer to your question.
import Stripe
class PaymentCardEditorField: STPPaymentCardTextField {
func setExistingCard(card: STPCardParams) {
replaceField("numberField", withValue: card.number!)
replaceField("expirationField", withValue: String(format: "%02d/%02d", card.expMonth, (card.expYear % 100)))
replaceField("cvcField", withValue: card.cvc!)
}
func replaceField(memberName: String, withValue value: String) {
let field = self.valueForKey(memberName) as! UITextField
let delegate = self as! UITextFieldDelegate
let len = field.text?.characters.count
if delegate.textField?(field, shouldChangeCharactersInRange: NSMakeRange(0, len!), replacementString: value) ?? false {
field.text = value
}
}
}
And then call the setExistingCard
func userDidProvideCreditCardInfo(cardInfo: CardIOCreditCardInfo!, inPaymentViewController paymentViewController: CardIOPaymentViewController!) {
let card: STPCardParams = STPCardParams()
card.number = info.cardNumber
card.expMonth = info.expiryMonth
card.expYear = info.expiryYear
card.cvc = info.cvv
paymentTextField.setExistingCard(card)
}
Works like a charm.
Follow this thread for potential update to Stripe SDK for in built support in the future.
https://github.com/stripe/stripe-ios/issues/127
I got a much cleaner answer from here
let cardParams = STPCardParams()
cardParams.number = "4242424242424242"
cardParams.expMonth = 07 // this data type is UInt and *not* Int
cardParams.expYear = 19 // this data type is UInt and *not* Int
cardParams.cvc = "123"
let paymentField = STPPaymentCardTextField()
paymentField.cardParams = cardParams //the paymentTextField will now show the cc #, exp, and cvc from above
You can get other test cards numbers like Mastercard, Amex, and Discover from here from Stripe
I am trying to add localised prices via labels to my shop section of my game. I am struggling to get it to work.
This is 1 part of the code for the purchase bit, its pretty standard.
I have an enum for all my product identifiers.
enum ProductID: String {
case product1
case product2
}
and the standard payment code.
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
println("add payment")
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
print("buy, ok unlock iap here")
print(p.productIdentifier)
let productIdentifier = transaction.payment.productIdentifier
switch productIdentifier {
case ProductID.product1.rawValue:
print("Product 1 bought")
//do something
case ProductID.product2.rawValue:
print("Product 2 bought")
//do something
default:
print("IAP not setup")
}
}
I use the following extension for fetching the localized price, again pretty boiler plate
extension SKProduct {
var localizedPrice: String {
let formatter = NSNumberFormatter()
formatter.numberStyle = .CurrencyStyle
formatter.locale = self.priceLocale
return formatter.stringFromNumber(self.price) ?? "\(price)"
}
}
So in my price labels that are underneath product buttons how do i got about adding each products localised price
priceProduct1Label.text = "\(?.localizedPrice)"
priceProduct2Label.text = "\(?.localizedPrice)"
What do I type for the question marks basically?. The videos and tutorial I read either don't tell you or they don't use a switch statement for the products.
Thank you very much in advance for any replies
I offer a slight variation on crashoverride777's answer above for, rather than a label, a button, and to draw not only the localized price from iTunes Connect but also the other content - the localizedTitle - that makes up the button title:
func loadBuy00ButtonTitle() {
for product in productArray {
let prodID = product.productIdentifier
if(prodID == "com.YourCompanyNameGoesHere.YourAppNameGoesHere.buy00Button") {
let prod = product
buy00Button.setTitle("Buy \(prod.localizedTitle): \(prod.localizedPrice())", for: .normal)
}
}
}
I'm in the USA and thus my button title reads something like: "Buy 100 coins: $0.99" - where for that product (purchase) "100 coins" is the localized title and "$0.99" is the localized price.
And again with minor variations, I, too, use the same extension he provides (in his question), which I place in a separate Swift file:
import StoreKit
extension SKProduct {
func localizedPrice() -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = self.priceLocale
return formatter.string(from: self.price)!
}
}
Ben Dodson's SKProduct localized price extension in Swift
I figured it out finally. I basically took the same code that is used to identify a product when one of the buy buttons is pressed. I just replaced the line that loads the buyProduct() function with the label details. Such as this example of one of my price labels.
func loadPriceLabel1() {
for product in products where product.productIdentifier == ProductID.product1.rawValue {
self.titleLabel.text = "\(product.localizedTitle)"
self.priceLabel1.text = "\(product.localizedPrice)"
etc
}
}
I load the label functions after the product request is finished, right after the buy buttons become enabled. Probably a solution slightly on the clunky side but it works great. If anyone has any suggestions for improvements please let me know.
You are looking for an instance of SKProduct, of which you can load using one or more product identifiers through an SKProductsRequest:
import StoreKit
class MyStore: NSObject, SKProductsRequestDelegate {
func loadProductsForProductIdentifiers( productIdentifiers: Set<String ) {
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers )
productRequest.delegate = self ///< Make sure to set a delegate
productRequest.start()
}
// MARK: - SKProductsRequestDelegate
func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
for object in response.products {
if let product = object as? SKProduct {
// Store these products and use their `productIdentifier` property to identify them
}
}
}
}