Swift UIAlert doesn't waiting user response - swift

func permissionInit() {
Task{
addListViewModel?.cameraPermission.accept(await chkCameraPermission())
addListViewModel?.photoLibraryPermission.accept(await chkPhotoLibraryPermission())
addListViewModel?.motionPermission.accept(await chkMotionPermission())
}
}
private func chkCameraPermission() async -> Bool{
let mediaType = AVMediaType.video
await AVCaptureDevice.requestAccess(for: mediaType)
let mediaAuthoriztionStatus = AVCaptureDevice.authorizationStatus(for: mediaType)
switch mediaAuthoriztionStatus{
case .authorized:
print("ShopUp Camera Permission True")
return true
case .denied, .restricted, .notDetermined:
print("ShopUp Camera Permission False")
warningAlert(title: "권한 오류", infoMativeMsg: "")
return false
default:
return false
}
}
private func chkPhotoLibraryPermission() async -> Bool {
let status = await PHPhotoLibrary.requestAuthorization(for: .readWrite)
switch status {
case .authorized:
print("ShopUp Photo Permission True")
return true
case .denied, .restricted, .notDetermined:
print("ShopUp Photo Permission False")
warningAlert(title: "권한 오류", infoMativeMsg: "")
return false
default:
return false
}
}
private func chkMotionPermission() async -> Bool{
let coreMotionGranted = CMPedometer.authorizationStatus()
switch coreMotionGranted {
case .authorized:
print("ShopUp CoreMotion Permission True")
return true
case .notDetermined, .restricted, .denied :
print("ShopUp CoreMotion Permission False")
warningAlert(title: "권한 오류", infoMativeMsg: "")
return false
default:
return false
}
}
func warningAlert(title: String, infoMativeMsg: String, completionHandler: Void? = nil) {
let alert = UIAlertController(title: title, message: infoMativeMsg, preferredStyle: .alert)
if completionHandler != nil {
let okAction = UIAlertAction(title: "확인", style: .default, handler: {_ in completionHandler})
alert.addAction(okAction)
}else {
let okAction = UIAlertAction(title: "확인", style: .default)
alert.addAction(okAction)
}
self.present(alert, animated: true, completion: completionHandler != nil ? {completionHandler!} : nil)
}
I added UIAlert in ViewController but it doesn't wait user response and showing error.
I also tried await on self.present but not working too.
permissionInit has an await but it doesn't seem to work.
2023-01-09 14:45:37.015435+0900 ShopUp[544:94537] [Presentation] Attempt to present <UIAlertController: 0x12c03e000> on <UINavigationController: 0x12d019c00> (from <ShopUp.AddListViewController: 0x12ce08350>) while a presentation is in progress.
2023-01-09 14:45:37.015644+0900 ShopUp[544:94537] [Presentation] Attempt to present <UIAlertController: 0x12d07b800> on <UINavigationController: 0x12d019c00> (from <ShopUp.AddListViewController: 0x12ce08350>) while a presentation is in progress.
I would like to show UIAlert in oder.
I would be grateful if you could let me know which part is wrong.

You are calling three functions, all of which try to present an alert at the same time (or nearly so.) Only the first one succeeds because a view controller can only present a single other view controller. The other two fail which is why you get the two error messages.
Here is one way to concatenate the three requests so that each one will wait until the previous ones are complete:
extension UIViewController {
func permissionInit() {
let avCapture = Observable.createAsync { await AVCaptureDevice.requestAccess(for: .video) }
.filter { !$0 }
.observe(on: MainScheduler.instance)
.flatMap { [weak self] _ in self?.warningAlert(title: "권한 오류", infoMativeMsg: "") ?? Observable.empty() }
let phPhoto = Observable.createAsync { await PHPhotoLibrary.requestAuthorization(for: .readWrite) }
.filter { $0 != .authorized }
.observe(on: MainScheduler.instance)
.flatMap { [weak self] _ in self?.warningAlert(title: "권한 오류", infoMativeMsg: "") ?? Observable.empty() }
let cmPedo = Observable.just(CMPedometer.authorizationStatus())
.filter { $0 != .authorized }
.flatMap { [weak self] _ in self?.warningAlert(title: "권한 오류", infoMativeMsg: "") ?? Observable.empty() }
_ = Observable.concat(avCapture, phPhoto, cmPedo)
.subscribe(onNext: {
print("all requests complete.")
})
}
func warningAlert(title: String, infoMativeMsg: String) -> Observable<Void> {
Observable.deferred {
let result = PublishSubject<Void>()
let alert = UIAlertController(title: title, message: infoMativeMsg, preferredStyle: .alert)
let okAction = UIAlertAction(title: "확인", style: .default, handler: { _ in result.onSuccess(()) })
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
return result
}
}
}
extension Observable {
static func createAsync(_ asyncFunc: #escaping () async throws -> Element) -> Observable<Element> {
Observable.create { observer in
let task = Task {
do {
observer.onSuccess(try await asyncFunc())
} catch {
observer.onError(error)
}
}
return Disposables.create { task.cancel() }
}
}
}
public extension ObserverType {
func onSuccess(_ element: Element) -> Void {
onNext(element)
onCompleted()
}
}
The key is in using the concat operator which will only subscribe to one Observable at a time. It waits until an Observable stops before subscribing to the next one.
Learn more in this article: Recipes for Combining Observables in RxSwift
Lastly, I also agree with HangarRash.

Related

How can I create SignIn and SignOut with 1 button in RxSwift?

Here my code to change text in button:
func transform(input: Input) -> Output {
let tappedSigninCheck = input.signinTrigger
.scan(false) { lastState, _ in
return !lastState }
let singupButtonTitle = tappedSigninCheck.map {
return $0 == true ? "Sign Out" : "Sign In"
}
return Output(signinButtonTitle: singupButtonTitle)
}
Now when I clicked SignIn, button text will change to "Sign Out" and I want when click Sign Out, sign out will display 1 alert and when click Yes in alert then button text change to Sign In.
My problem: whenever I click in button then button text change text :(((
func bindViewModel() {
let signoutTrigger = signinButton.rx.tap.flatMap {
return Observable<Void>.create { (observer) -> Disposable in
let alert = UIAlertController(title: "Are you sure to sign out?",
message: nil,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "No",
style: .cancel,
handler: nil))
alert.addAction(UIAlertAction(title: "Yes",
style: .destructive,
handler: { _ in
observer.onNext(Void())
}))
self.present(alert, animated: true)
return Disposables.create()
}
}
let input = ProfileViewModel.Input(signinTrigger: signinButton.rx.tap.asDriver(),
signoutTrigger: signoutTrigger.asDriver(onErrorJustReturn: Void()))
let output = profileViewModel.transform(input: input)
output
.signinButtonTitle
.drive(signinButton.rx.title)
.disposed(by: disposeBag)
}
For this you will need a feedback loop, and that calls for a Subject. I suggest you do it something like below. Also note that you forgot to emit a completed event on your alert.:
class ViewModel {
struct Input {
let logButton: Observable<Void>
let okayButton: Observable<Void>
}
struct Output {
let buttonTitle: Observable<String>
let displayAlert: Observable<Void>
}
private enum Action {
case tapped
case okay
}
private enum State {
case offline
case online
case check
}
func transform(_ input: Input) -> Output {
let state = Observable.merge(
input.logButton.map(to: ViewModel.Action.tapped),
input.okayButton.map(to: ViewModel.Action.okay)
)
.scan(ViewModel.State.offline) { state, action in
switch (state, action) {
case (.offline, .tapped):
return .online
case (.online, .tapped):
return .check
case (.check, .okay):
return .offline
case (.check, .tapped):
return .check
default:
assert(false)
return state
}
}
.share()
let buttonTitle = state
.map { $0 == .offline ? "Log In" : "Log Out" }
let displayAlert = state
.filter { $0 == .check }
.map(to: ())
return Output(
buttonTitle: buttonTitle,
displayAlert: displayAlert
)
}
}
class ViewController: UIViewController {
var button: UIButton!
var viewModoel: ViewModel!
let disposeBag = DisposeBag()
func bind() {
let logout = PublishSubject<Void>()
let input = ViewModel.Input(
logButton: button.rx.tap.asObservable(),
okayButton: logout
)
let output = viewModoel.transform(input)
output.buttonTitle
.bind(to: button.rx.title(for: .normal))
.disposed(by: disposeBag)
output.displayAlert
.flatMap { [unowned self] _ in
self.areYouSure()
}
.bind(to: logout)
.disposed(by: disposeBag)
}
func areYouSure() -> Observable<Void> {
Observable.create { [unowned self] observer in
let alert = UIAlertController(
title: "Are you sure to sign out?",
message: nil,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(
title: "Yes", style: .destructive, handler: { _ in
observer.onNext(())
observer.onCompleted()
}
))
alert.addAction(UIAlertAction(
title: "No", style: .default, handler: { _ in
observer.onCompleted()
}
))
self.present(alert, animated: true)
return Disposables.create()
}
}
}

UIButton click, Alamofire POST call, doesnt performSegue with a successful Firebase Login

I have a simple button action
I do verify the email and password before going into this, but this is my Firebase code. When you click on the button, it will get into the VerifyUserInformation function and the response will spit out. Basically, the segue's in VerifyUserInformation will not run for me, the dismiss function doesn't dismiss the modal (present full screen) either.
What can be done to fix this?
Auth.auth().signIn(withEmail: emailOutlet.text!, password: passwordOutlet.text!) { [weak self] user, error in
guard let strongSelf = self else { return }
if let error = error {
self!.displaySnackbar(messageString: "\(error.localizedDescription)")
return
}
self!.preferences.setValue(true, forKey:SHARED_PREF_USER_LOGGED_IN_KEY)
var firstTimeUser = self!.preferences.bool(forKey:FIRST_TIME_USER)
print("\(self!.TAG) FirstTimeUser: \(firstTimeUser)")
if (firstTimeUser) {
print("\(self!.TAG) This is the first time the user is using the application.")
self?.VerifyUserInformation(firebaseId: "\(Auth.auth().currentUser!.uid)")
} else {
print("\(self!.TAG) User can head into the Application.")
self!.performSegue(withIdentifier: "MainScreen", sender: nil)
self?.progressBar.isHidden = true
self!.loginButtonOutlet.isHidden = false
}
}
To verify the user, I run this function.
func VerifyUserInformation(firebaseId: String) {
let urlString = ADD_USER_FOR_ACCOUNT
let param = [
FROM_APP: "true",
USER_FIREBASE_ID: firebaseId,
GET_USER_ACCOUNT_INFORMATION: "true"
] as [String : Any]
AF.request(urlString, method: .post, parameters: param ,encoding: URLEncoding.default).responseJSON {
response in
switch response.result {
case .success:
print("\(self.TAG)\n***Response***\n\(response)\n***END***")
if let result = response.value {
let JSON = result as! NSDictionary
let errorResponse = JSON["error"] as! Int
if (errorResponse == 1) {
print("\(self.TAG) Error verifying the user.")
self.displaySnackbar(messageString: "Error verifying user. Try again.")
} else {
print("\(self.TAG) User is verified")
let messageResponse = JSON["message"] as! String
if (messageResponse == "user has items") {
print("\(self.TAG) User has items, go into MainScreen")
DispatchQueue.main.async {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.performSegue(withIdentifier: "MainScreen", sender: nil)
self.dismiss(animated: false, completion: nil)
self.preferences.setValue(false, forKey:FIRST_TIME_USER)
self.loginButtonOutlet.isHidden = false
self.progressBar.isHidden = true
}
}
} else {
print("\(self.TAG) User has 0 items, go into Second Onboarding")
DispatchQueue.main.async {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.performSegue(withIdentifier: "SecondOnBoarding", sender: nil)
self.dismiss(animated: false, completion: nil)
self.loginButtonOutlet.isHidden = false
self.progressBar.isHidden = true
}
}
}
}
}
break
case .failure(let error):
self.loginButtonOutlet.isHidden = false
self.progressBar.isHidden = true
self.displaySnackbar(messageString: "Error getting user information. Try again.")
print("\(self.TAG) \(error)")
}
}
}
After removing the dismiss(), it started to work.

How to disable a button that serves for an in-App Purchase, without putting it in another thread?

In my navigation bar i have a button to buy something in the app.
let acheterSuppressionPub: UIButton!
let acheterSuppressionPubButtonItem = UIBarButtonItem(customView: acheterSuppressionPub)
acheterSuppressionPub.addTarget(self, action: #selector(ProposerSupprimerPub), for: .touchUpInside)
#objc private func ProposerSupprimerPub() {
if self.adsView.isHidden == false {
afficherSupprimerForAllPubAlert()
}
}
In the view controller, i have this:
// désactiver les boutons d'achats
self.acheterSuppressionPub.isEnabled = false
self.acheterSuppressionPub.isHidden = true
// Purchase
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID: NSSet = NSSet(objects:
"com.KingsFit-W.myfood.removeads")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
Here is the alert to propose the purchase:
private func afficherSupprimerForAllPubAlert() {
let alert = UIAlertController(title: NSLocalizedString("Souhaitez-vous enlever définitivement toutes les bannières publicitaires?", comment: ""), message: "\n" + NSLocalizedString("Coût ", comment: "") + NSLocalizedString("xxx€", comment: ""), preferredStyle: .alert)
alert.setValue(attributedString, forKey: "attributedTitle")
alert.addAction(UIAlertAction(title: NSLocalizedString("Enlever la publicité", comment: ""), style: .default, handler: { (action) in
print("remove ads began")
for product in self.listeDesAchats {
let prodID = product.productIdentifier
if(prodID == "com.xxxxx.xxxxx.removeads") {
self.unAchat = product
self.buyProduct()
}
}
}))
alert.addAction(UIAlertAction(title: NSLocalizedString("Restorer mon achat", comment: ""), style: .default, handler: { (action) in
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}))
alert.addAction(UIAlertAction(title: NSLocalizedString("Annuler", comment: ""), style: .cancel, handler: { (action) in
return
}))
present(alert, animated: true)
}
Here is the functions to buy the product:
func buyProduct() {
print("Acheter " + unAchat.productIdentifier)
let pay = SKPayment(product: unAchat)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(pay as SKPayment)
}
func removeAds() {
self.adsView.isHidden = true
self.acheterSuppressionPub.isHidden = true
self.acheterSuppressionPub.isEnabled = false
}
Here is the function where the problem is:
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
listeDesAchats.append(product)
}
/////////////////////////////////////////////////////////////////
self.acheterSuppressionPub.isEnabled = true // it says: -[UIButton setEnabled:] must be used from main thread only
self.acheterSuppressionPub.isHidden = false
///////////////////////////////////////////////////////
}
it says: -[UIButton setEnabled:] must be used from main thread only
Here are the protocol functions:
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("transactions restored")
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "com.KingsFit-W.myfood.removeads":
print("remove ads")
removeAds()
default:
print("IAP not found")
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("add payment")
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print(trans.error as Any)
switch trans.transactionState {
case .purchased:
print("buy ok, unlock IAP HERE")
print(unAchat.productIdentifier)
let prodID = unAchat.productIdentifier
switch prodID {
case "xxxxxxxxx.removeads":
print("remove ads")
removeAds()
default:
print("IAP not found")
}
queue.finishTransaction(trans)
case .failed:
print("buy error")
queue.finishTransaction(trans)
break
default:
print("Default")
break
}
}
}
How could i disable and hidden the acheterSuppressionPub button, without putting it in a another thread?
I want to deactivate it to prevent the user from pressing it again when the network request has not yet finished.
All communication with the interface must be on the main thread. The standard way to step out to the main thread to talk to the interface is:
DispatchQueue.main.async {
self.acheterSuppressionPub.isEnabled = true
self.acheterSuppressionPub.isHidden = false
}
Similarly:
func removeAds() {
DispatchQueue.main.async {
self.adsView.isHidden = true
self.acheterSuppressionPub.isHidden = true
self.acheterSuppressionPub.isEnabled = false
}
}

AVSpeechRecognizer: required condition is false: _recordingTap == nil error in Swift3

I have no idea why I have this error. The error I got is
Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: _recordingTap == nil'
UPDATE
Actually, this works, but after several times, suddenly the button is disabled and the mic no longer is in action. Then it causes the error and get crashed.
Could you please help me with this?
class ViewController: UIViewController, SFSpeechRecognizerDelegate, UITextViewDelegate, AVSpeechSynthesizerDelegate {
#IBOutlet weak var myTextView: UITextView!
#IBOutlet weak var microphoneButton: UIButton!
private let speechRecognizer = SFSpeechRecognizer(locale: Locale.init(identifier: "en-US"))!
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
private var recognitionTask: SFSpeechRecognitionTask?
private let audioEngine = AVAudioEngine()
var rightButton = UIBarButtonItem()
override func viewDidLoad() {
super.viewDidLoad()
microphoneButton.isEnabled = false
speechRecognizer.delegate = self
speechRecognizerFunc()
myTextView.delegate = self
myTextView.isUserInteractionEnabled = false
}
#IBAction func cancelButton(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
}
func speechRecognizerFunc(){
SFSpeechRecognizer.requestAuthorization {
(authStatus) in
var isButtonEnabled = false
switch authStatus {
case .authorized:
isButtonEnabled = true
case .denied:
isButtonEnabled = false
print("User denied access to speech recognition")
case .restricted:
isButtonEnabled = false
print("Speech recognition restricted on this device")
case .notDetermined:
isButtonEnabled = false
print("Speech recognition not yet authorized")
}
OperationQueue.main.addOperation() {
self.microphoneButton.isEnabled = isButtonEnabled
}
}
}
#IBAction func microphoneTapped(_ sender: AnyObject) {
if audioEngine.isRunning {
audioEngine.stop()
recognitionRequest?.endAudio()
microphoneButton.isEnabled = false
microphoneButton.setTitle("Start", for: .normal)
myTextView.text = ""
} else {
myTextView.text = "Say something. I'm listening...."
startRecording()
microphoneButton.setTitle("Stop", for: .normal)
}
}
func startRecording() {
if recognitionTask != nil {
recognitionTask?.cancel()
recognitionTask = nil
}
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker)
try audioSession.setMode(AVAudioSessionModeMeasurement)
try audioSession.setActive(true, with: .notifyOthersOnDeactivation)
} catch {
print("audioSession properties weren't set because of an error.")
}
recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
guard let inputNode = audioEngine.inputNode else {
fatalError("Audio engine has no input node")
}
guard let recognitionRequest = recognitionRequest else {
fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object")
}
recognitionRequest.shouldReportPartialResults = true
recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest, resultHandler: {
(result, error) in
var isFinal = false
if result != nil {
self.myTextView.text = result?.bestTranscription.formattedString
isFinal = (result?.isFinal)!
}
if error != nil || isFinal {
self.audioEngine.stop()
inputNode.removeTap(onBus: 0)
self.recognitionRequest = nil
self.recognitionTask = nil
self.microphoneButton.isEnabled = true
self.performSegue(withIdentifier: "nv", sender: nil)
}
})
let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in
self.recognitionRequest?.append(buffer)
}
audioEngine.prepare()
do {
try audioEngine.start()
} catch {
print("audioEngine couldn't start because of an error.")
}
}
func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
if available {
microphoneButton.isEnabled = true
} else {
microphoneButton.isEnabled = false
}
}
func popUpAlert(title: String, msg: String){
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
let okay = UIAlertAction(title: "Okay", style: .default, handler: nil)
alert.addAction(okay)
self.present(alert, animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "nv" {
let vc = segue.destination as! SpeechTableViewController
vc.text = self.myTextView.text
print("ViewControoler: text value: \(textValue)")
}
}
}
You can click start record button fast to repeat this crash, the reason is if you stop audioEngine, audioEngine.inputNode still there. You need add this to stop record.
audioEngine.stop()
recognitionRequest?.endAudio()
audioEngine.inputNode?.removeTap(onBus: 0)

In app purchase worked in Swift 2 but doesn't now that I've updated to Swift 3 and Xcode 8

As the title says, I had code for an in app purchase to remove ads working in Swift 2 and then updated to Swift 3 and updated the code as well. It gets to the point where it sends the payment request to Apple but then won't run func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) called in func buyProduct(). Here's the code:
// MainScene.swift
// Contain
//
// Created by Phil Javinsky III on 9/16/16.
// Copyright © 2016 Phil Javinsky III. All rights reserved.
//
import SpriteKit
import StoreKit
let defaults = UserDefaults.standard
var noAds: Bool = false
var fetched: Bool = false
var product = SKProduct()
var productID = "containRemoveAds"
var readyForIAP: Bool = false
class MainScene: SKScene, SKProductsRequestDelegate, SKPaymentTransactionObserver {
var touchLocation: CGPoint = CGPoint.zero
var start, leaderboards, rate, removeAds: SKSpriteNode!
override func didMove(to view: SKView) {
SKPaymentQueue.default().add(self)
if defaults.bool(forKey: "purchased") {
print("Already purchased")
bannerAd.isHidden = true
noAds = true
}
else {
print("Not purchased")
if !fetched {
print("Fetching")
getProductInfo()
fetched = true
}
}
start = self.childNode(withName: "start") as! SKSpriteNode
leaderboards = self.childNode(withName: "leaderboards") as! SKSpriteNode
leaderboards.position = CGPoint(x: 0, y: -1150)
rate = self.childNode(withName: "rate") as! SKSpriteNode
rate.position = CGPoint(x: 0, y: -1350)
removeAds = self.childNode(withName: "removeAds") as! SKSpriteNode
removeAds.position = CGPoint(x: 0, y: -1550)
SKPaymentQueue.default().remove(self)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touchLocation = touches.first!.location(in: self)
if start.contains(touchLocation) {
let game: GameScene = GameScene(fileNamed: "GameScene")!
game.scaleMode = .aspectFit //.resizeFill?
view?.presentScene(game, transition: SKTransition.fade(withDuration: 1))
}
else if leaderboards.contains(touchLocation) {
//showLeaderboard()
}
else if rate.contains(touchLocation) {
/*let url = NSURL(string: "itms-apps://itunes.apple.com/us/app/whack-a-diglett/id1141187647?ls=1&mt=8")
if UIApplication.shared.canOpenURL(url! as URL) {
UIApplication.shared.openURL(url! as URL)
}*/
}
else if removeAds.contains(touchLocation) {
let alert = UIAlertController.init(title: "Remove Ads", message: "Remove ads or restore a previous purchase.", preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Remove Ads", style: .default, handler:
{ action -> Void in
print("Remove Ads")
if readyForIAP {
self.buyProduct()
}
else {
print("Not ready for IAP")
let alert = UIAlertController.init(title: "Error", message: "Something went wrong, try again.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
self.view?.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
}))
alert.addAction(UIAlertAction(title: "Restore Purchase", style: .default, handler:
{ action -> Void in
print("Restore purchase")
if (SKPaymentQueue.canMakePayments()) {
SKPaymentQueue.default().restoreCompletedTransactions()
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.view?.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
func getProductInfo() {
//print("About to fetch the products")
// Check if allowed to make the purchase
if SKPaymentQueue.canMakePayments() {
let productIdentifier: NSSet = NSSet(object: productID)
let productsRequest: SKProductsRequest = SKProductsRequest(productIdentifiers: productIdentifier as! Set<String>)
productsRequest.delegate = self
productsRequest.start()
//print("Fetching Products")
}
else {
print("can't make purchases")
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
var products = response.products
if (products.count != 0) {
product = products[0]
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
readyForIAP = true
print("Ready for IAP")
}
else {
print("Product not found")
}
}
func buyProduct() {
print("Sending payment request to Apple")
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment as SKPayment)
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("Received payment transaction response from Apple")
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
print("Product Purchased")
defaults.set(true, forKey: "purchased")
bannerAd.isHidden = true
noAds = true
SKPaymentQueue.default().finishTransaction(transaction)
case .failed:
print("Purchased Failed")
SKPaymentQueue.default().finishTransaction(transaction)
case .restored:
print("Restored")
let alert: UIAlertController = UIAlertController(title: "Restored", message: "Purchase restored, ads removed.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
self.view?.window?.rootViewController?.present(alert, animated: true, completion: nil)
defaults.set(true, forKey: "purchased")
bannerAd.isHidden = true
noAds = true
SKPaymentQueue.default().finishTransaction(transaction)
default:
print("DEFAULT")
break
}
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("Error Fetching product information");
}
}
Why are you removing the transaction observer in didMoveToView? That makes no sense.
Add the transaction observer in didMoveToView
SKPaymentQueue.default().add(self)
and only remove it when your app closes.
Maybe its also a good idea to move your IAP code into another helper class or something, your code is hard to read like this.
Hope this helps