Post

Replies

Boosts

Views

Activity

How to handle a non-consumable in app purchase when SKPaymentQueue is deprecated
I use the code below for a non-consumable in-app purchase in my apps. Has anybody worked out how to handle this without using any of the deprecated items? SKPaymentQueue - deprecated, SKPayment - deprecated, SKProduct - deprecated, transactionState - deprecated, SKPaymentTransaction - deprecated, finishTransaction - deprecated func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool { true } func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { switch transaction.transactionState { case .purchasing: break case .purchased: SKPaymentQueue.default().finishTransaction(transaction) // Hide the restore button navigationItem.setRightBarButton(nil, animated: true) // Set the ProVerion in the Db to true IAPHandler.setProVersionToPurchased() // Also hide the Purchase button UIView.animate(withDuration: 1.0, animations: { [weak self] in self?.purchaseBtn_Outlet.alpha = 0 }) { [weak self] (success) in if self!.theDevice.isOneOf(K.Device_Groups.SE_3_iPhone8) { self?.segControlTop_Constraint.constant = 10 } else if self!.theDevice.isPhone { self?.segControlTop_Constraint.constant = 30 } } case .failed: if let error = transaction.error { let errorDescription = error.localizedDescription print("Transaction failed due to error: \(errorDescription)") } case .restored: SKPaymentQueue.default().finishTransaction(transaction) // Hide the restore button navigationItem.setRightBarButton(nil, animated: true) // Set the ProVerion in the Db to true IAPHandler.setProVersionToPurchased() // Also hide the Purchase button UIView.animate(withDuration: 1.0, animations: { [weak self] in self?.purchaseBtn_Outlet.alpha = 0 }) { [weak self] (success) in if self!.theDevice.isOneOf(K.Device_Groups.SE_3_iPhone8) { self?.segControlTop_Constraint.constant = 10 } else if self!.theDevice.isPhone { self?.segControlTop_Constraint.constant = 30 } } case .deferred: break @unknown default: if let error = transaction.error { let errorDescription = error.localizedDescription print("Transaction failed due to error: \(errorDescription)") } break } } } // Sets the purchase to true in the Db class IAPHandler: NSObject { //Get the ProVersion Status static func isProVersionPurchased() -> Bool { let VC_String = "IAPHandler" var theStatus = false do { let settings = try Database.shared.databaseConnection!.read { db in try My_Settings.fetchOne(db) } let theStatusText = settings?.ProVersion ?? "false" theStatus = theStatusText == "true" ? true : false } catch { print("Getting the ProVersion Status failed! \(VC_String) \(error)") } return theStatus } // Set ProVersion to true. static func setProVersionToPurchased() { let VC_String = "IAPHandler" do { try Database.shared.databaseConnection!.write { db in try db.execute(sql: "UPDATE My_Settings SET ProVersion = :proVersion WHERE Settings_ID = :id", arguments: ["proVersion": "true", "id": 1]) } } catch { print("Update set pro version, failed! \(VC_String)s \(error)") } } }// End of class
1
0
387
Jan ’25
The firing order of NotificationCenter observing when the keyboard appears and textViewDidBeginEditing has been changed.
If you are seeing the same thing please reply to this post. The firing order was changed when tapping a textView or textField. I first noticed this in iOS 15.6.1 on my physical device. If I send the app to a physical device running iOS 15.5 everything worked fine. Once I updated the device to iOS 15.6.1 or higher the change in firing order broke the ability to get the keyboard height when the textView was first tapped. It's happening on both iPad and iPhone. In Xcode 13.4.1, iOS 15.5 and before. keyboardWillShow fired then textViewDidBeginEditing fired after. This allowed to get the keyboard height before the textView became active and thus move the textView up the proper amount. In Xcode 14.0.1, or anything above iOS 15.5. textViewDidBeginEditing fires then keyboardWillShow fires after. So if you're moving the textview up to clear the keyboard it won't work right anymore because you won't get the keyboard height until after the textView has become firstResponder. The firing order was also changed for textFields. I thought about hard-coding in the keyboard heights for each device but that really doesn't make sense. That and what if Apple changes the keyboard height. I have filed a bug report but so far no response. If you're using notificationCenter to get the keyboard height please check if your app is broken too. If so, please file a bug report so we can get some traction. I have several apps I'm waiting to release and this is holding me up. You can use the code below to check the before and after firing order. Keep in mind that the textView delegate needs to be the view. In viewDidLoad I have this. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) The selector for NotificationCenter.default.addObserver // MARK: - get the keyboard height @objc func keyboardWillShow(notification: NSNotification) { if let keyboardRectValue = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { print("keyboardHeight ran \(keyboardHeight)") } } Then I have: // MARK: - TextView Editing Begin. func textViewDidBeginEditing(_ textView: UITextView) { print("textViewDidBeginEditing ran") }
2
1
1.1k
Oct ’22
Setting up keyboard avoidance to a textview using keyboardLayoutGuide
I'm setting up keyboard avoidance to a textview. Objectives: Use keyboardLayoutGuide.followsUndockedKeyboard Allow for floating keyboard on iPad Move the textView up to clear the keyboard when it's tapped Move the textView back when the keyboard is hidden or when the textView is not firstResponder View layout for testing: Three controls. A textField A TextView A UIView The constraint on the bottom of the textView is 15 pts from the top of the UIView. This constraint is set to a Priority of 750 The code I have works fine except when the iPad is rotated. keyboardWillHideNotification fires when the rotation occurs even though the keyboard stays undocked. This causes the textView to drop to its home position and then pop back up again. It's very goofy looking. The textView should hug the top of the keyboard. I tried using textViewDidEndEditing instead of keyboardWillHide but that isn't much better. Anybody have any ideas on making the textView hug the top of the keyboard when the iPad is rotated? class ViewController: UIViewController, UITextViewDelegate { @IBOutlet weak var myTextView: UITextView! private var buttomConstraint: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() myTextView.translatesAutoresizingMaskIntoConstraints = false buttomConstraint = myTextView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor) view.keyboardLayoutGuide.followsUndockedKeyboard = true NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) hideKeyboard() } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { } @objc func keyboardWillHide(notification: NSNotification) { print("Keyboard will hide") if myTextView.isFirstResponder { UIView.animate(withDuration: 0.3) { [weak self] in self?.buttomConstraint.isActive = false self?.view.layoutIfNeeded() } } else { print("Somethibg") } } @objc func keyboardWillShow(notification: NSNotification) { if myTextView.isFirstResponder { UIView.animate(withDuration: 0.3) { [weak self] in self?.buttomConstraint.isActive = true self?.view.layoutIfNeeded() } } else { buttomConstraint.isActive = false } } func textViewDidBeginEditing(_ textView: UITextView) { // UIView.animate(withDuration: 0.3) { [weak self] in // self?.buttomConstraint.isActive = true // self?.view.layoutIfNeeded() // } } func textViewDidEndEditing(_ textView: UITextView) { // UIView.animate(withDuration: 0.3) { [weak self] in // self?.buttomConstraint.isActive = false // self?.view.layoutIfNeeded() // } } } extension ViewController { // MARK: This dismisses the keyBoard when the view it tapped func hideKeyboard() { let tap: UITapGestureRecognizer = UITapGestureRecognizer( target: self, action: #selector(ViewController.dismissKeyboard)) tap.cancelsTouchesInView = false view.addGestureRecognizer(tap) } @objc func dismissKeyboard() { view.endEditing(true) } }
2
0
2.4k
Oct ’22
.main is going to be deprecated
I have this code in the sceneDelegate of my app. I understand that .main is going to be deprecated. The warning says to use view?.window?.windowScene?.screen but there is no view in sceneDelegate. How can I change let screenSize = UIScreen.main.fixedCoordinateSpace.bounds to not use .main? Thanks in advance.
8
0
4.8k
Dec ’22
In-App Purchase Restore - No observers found
When I execute a restore on my in-app purchase I'm getting a warning, however the restore is successfully executed. I think this is something new with Xcode 14.3. My test device is running iOS 16.4 This is the warning: <SKPaymentQueue: 0x283708a80>: No observers found that respond to "paymentQueue:shouldAddStorePayment:forProduct:", will not check for purchase intents It fires at this point in the code. If I comment out the first line, I don't get the warning however, the restore doesn't execute. Is anybody else seeing this or do I have something wrong in my code? I know it's only a warning but any help would be appreciated. @IBAction func restoreButtonTapped(_ sender: UIBarButtonItem) { SKPaymentQueue.default().add(self) SKPaymentQueue.default().restoreCompletedTransactions() } I've included the rest of the code just for a completeness. func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { switch transaction.transactionState { case .purchasing: //print("Purchase in progress...") break case .purchased: //print("Purchase Successful") SKPaymentQueue.default().finishTransaction(transaction) //print("Transaction Complete") // Hide the restore button navigationItem.setRightBarButton(nil, animated: true) //Set the BaseVerion in the Db to true IAPHandler.set_BaseVersion_To_Purchased() //Also hide the Purchase button UIView.animate(withDuration: 1.0, animations: { [weak self] in self?.purchaseButton.alpha = 0 }) { [weak self] (success) in self?.selector_Top_Constraint.constant = 30 } case .failed: if let error = transaction.error { let errorDescription = error.localizedDescription print("Transaction failed due to error: \(errorDescription)") } case .restored: SKPaymentQueue.default().finishTransaction(transaction) //print("Transaction Complete") // Hide the restore button navigationItem.setRightBarButton(nil, animated: true) // Set the BaseVerion in the Db to true IAPHandler.set_BaseVersion_To_Purchased() // Also hide the Purchase button UIView.animate(withDuration: 1.0, animations: { [weak self] in self?.purchaseButton.alpha = 0 }) { [weak self] (success) in self?.selector_Top_Constraint.constant = 30 } case .deferred: //print("Purchase Deferred") break @unknown default: if let error = transaction.error { let errorDescription = error.localizedDescription print("Transaction failed due to error: \(errorDescription)") } break } } } @IBAction func purchaseButtonTapped(_ sender: UIButton) { let theAlert = UIAlertController.init(title: K.Titles.pleaseChoose, message: nil, preferredStyle: .actionSheet) let theCancleAction = UIAlertAction(title: K.Titles.cancel, style: .cancel) let thePurchaseAction = UIAlertAction(title: K.DefaultList_Buttons.purchase_BaseVersion_Btn, style: .default) { [weak self] (action2) in if SKPaymentQueue.canMakePayments() { // User can make payments let paymentRequest = SKMutablePayment() paymentRequest.productIdentifier = self!.base_Product_ID SKPaymentQueue.default().add(self!) SKPaymentQueue.default().add(paymentRequest) } else { // User cannot make payments print("User cannot make payments") } } theAlert.addAction(thePurchaseAction) theAlert.addAction(theCancleAction) theAlert.setValue(NSAttributedString(string: theAlert.title ?? "", attributes: [.font : UIFont.systemFont(ofSize: (gDefaultTextSize - 2), weight: UIFont.Weight.semibold)]), forKey: "attributedTitle") let popOver = theAlert.popoverPresentationController popOver?.sourceView = sender popOver?.sourceRect = sender.bounds popOver?.permittedArrowDirections = .any present(theAlert, animated: true) }
5
3
6.1k
Nov ’23
invalid mode 'kCFRunLoopCommonModes'
invalid mode 'kCFRunLoopCommonModes' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debugI get this warning when I tap either of the switches shown below. I've tried capturing the switch state in a var and using that to trigger the do/catch statement but no joy. I've even tried pulling the do/catch into separate functions and I still get the warning. Has anybody else run into this and how did you fix it?@IBAction func greetingFormat_Tapped(_ sender: UISwitch) { let theQuery = theTable_Settings.filter(settingID == 1) if sender.isOn { do { if try Database.shared.databaseConnection!.run(theQuery.update(greeting_Format &lt;- "true")) &gt; 0 { greetingFormatLabel_Outlet.text = NSLocalizedString("HelloMrSmith_String", comment: "") } else { print("greeting format true not found") } } catch { print("greeting format true update failed! Error: \(error)") } } else { do { if try Database.shared.databaseConnection!.run(theQuery.update(greeting_Format &lt;- "false")) &gt; 0 { greetingFormatLabel_Outlet.text = NSLocalizedString("HiJoe_String", comment: "") } else { print("greeting format false not found") } } catch { print("greeting format false update failed! Error: \(error)") } } }@IBAction func nonrefundableSwitch_Tapped(_ sender: UISwitch) { let theQuery = theTable_Settings.filter(settingID == 1) var itsOn: String = "" if sender.isOn { itsOn = "true" } else { itsOn = "false" } if itsOn == "true" { do { if try Database.shared.databaseConnection!.run(theQuery.update(nonRefundable_Bool &lt;- "true")) &gt; 0 { depositDueLabel_Outlet.text = NSLocalizedString("nonRefunddepositisdue_String", comment: "") } else { print("nonRefundable true not found") } } catch { print("nonRefundable true update failed! Error: \(error)") } } else { do { if try Database.shared.databaseConnection!.run(theQuery.update(nonRefundable_Bool &lt;- "false")) &gt; 0 { depositDueLabel_Outlet.text = NSLocalizedString("depositisdue_String", comment: "") } else { print("nonRefundable false not found") } } catch { print("nonRefundable false update failed! Error: \(error)") } } }
34
2
25k
Nov ’23