In-App Purchase

Hi


I want to add to my app the ability to purchase something. My app is similar to Uber. What I mean is that I have a collection view, and I want to be able to associate a price for the type of ride ex. basic or luxury, and the duration of the ride. My app is set up where you choose in collection view what type of ride you want. Then there are 3 sets of buttons where you choose the length of the ride. What I wanted to know is how would I make it so that my app take the type of ride, and the duration and adds the two of them together on another screen, as well as displays what choices you made on the previous screen, and what would the code look like for that. The only thing that I have for the follow-up screen where I want my app to show the price and ask the user to pay is I made a view controller, and I connected it to the previous screen with a custom segue which has no code in it yet.

Answered by Claude31 in 323066022

So your question is not about IAP but about transferring data from one view to another.


What I wanted to know is how would I make it so that my app take the type of ride, and the duration and adds the two of them together on another screen


Let's call the view wwhere you define the ride the rideView and the view where you tell the price the price View

Proceed like this:

you have probably defined enum

enum TypeOfRide {
    case basic
    case luxury

    func description() -> String {
          switch self {
               case .basic : return "basic"
               case .luxury : return "luxury"
          }
}

enum LengthOfRide {
    case short
    case medium
    case long    

     func description() -> String {
          switch self {
               case .short : return "short"
               case .medium : return "medium"
               case .long : return "long"

          }
}


In RideView:

- you have a var to store the type of ride

var rideType : TypeOfRide

This var is set when you select the type either to .basic or .luxury in the collectionView


- you have a var to store the length of ride

var rideLength : LengthOfRide

This is set when you click a button, in the IBAction


In PriceView,

you define a var to store the price, the lenght and the type of ride

var price : Float?
var category : TypeOfRide?
var length: LengthOfRide?

and an IBOutlet where you will display the price

    @IBOutlet weak var priceLabel: UILabel!
    @IBOutlet weak var typeLabel: UILabel!
    @IBOutlet weak var lengthLabel: UILabel!


In RideView:

You should have a butto like Order

Control-drag from the button to the PriceViewController to define a segue

Give it an identifier as SegueToPrice


In prerare for segue

- you compute the price, according to your computation formula

- you pass the price to the destination VC


override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

     var ridePrice : Float = 0.0

     switch rideType {
        case .basic :
          switch rideLength {
               case .short : ridePrice = 5.0
               case .medium : ridePrice = 10.0
               case .long : ridePrice = 15.0
          }
        case .luxury :
          switch rideLength {
               case .short : ridePrice = 12.5
               case .medium : ridePrice = 25.0
               case .long : ridePrice = 40.0
          }
     }

          if segue.identifier == "SegueToPrice" {          // The identifier you have given to the segue
               if let destVC = segue.destination as? PriceView {
                    destVC.price = ridePrice
                    destVC.category = rideType
                    destVC.length = rideLength
               }
          }
     }



Finally, in PriceView

in viewDidLoad

priceLabel.text = price
typeLabel.text = category.description
lengthLabel.text = length.description

This is my code that's in the viewController:


@IBOutlet weak var driverGenderFemale: UIButton!

@IBOutlet weak var eitherGender: UIButton!

@IBOutlet weak var driverGenderMale: UIButton!

@IBOutlet weak var sixtyMinuteRide: UIButton!

@IBOutlet weak var ninetyMinuteRide: UIButton!

@IBOutlet weak var twoHourRide: UIButton!

@IBOutlet weak var rideDatePicker: UIDatePicker!

@IBOutlet weak var segueToPrice: UIButton!

let array:[String] = ["cheap", "basic", "luxury", "platinum"]


//Populate Collection View

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell

cell.rideHorizontalSwipe.image = UIImage(named: array[indexPath.row] + ".JPG")

return cell

}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

var ridePrice : Float = 0.0

switch rideType {

case .cheap :

switch rideLength {

case .sixtyMinuteRide : ridePrice = 5.0

case .ninetyMinuteRide : ridePrice = 10.0

case .twoHourRide : ridePrice = 15.0

}

case .basic :

switch rideLength{

case .sixtyMinuteRide : ridePrice = 12.5

case .ninetyMinuteRide : ridePrice = 25.0

case .twoHourRide : ridePrice = 40.0

}

case .Luxury:

switch rideLength{

case .sixtyMinuteRide : ridePrice = 12.0

case .ninetyMinuteRide : ridePrice = 12.0

case .twoHourRide : ridePrice = 12.0

}

case .platinum:

switch rideLength{

case .sixtyMinuteRide : ridePrice = 12.0

case .ninetyMinuteRide : ridePrice = 12.0

case .twoHourRide : ridePrice = 12.0

}

}

if segue.identifier == "SegueToPrice" { // The identifier you have given to the segue

if let destVC = segue.destination as? PriceView {

destVC.price = ridePrice

destVC.category = rideType

destVC.length = rideLength

}

}

}


The errors that I get in the viewController are:


Pattern cannot match values of type '[UIButton]!'

Pattern cannot match values of type '[UIButton]!'

Pattern cannot match values of type '[UIButton]!'

Cannot assign value of type '[UIButton]!' to type 'massageLength?'


In my PriceView I have this code:


import UIKit


class PriceView: UIViewController{

var price : Float?

var category : rideHorizontalSwipe?

var length: rideLength?

@IBOutlet weak var driverGenderPrice: UILabel!

@IBOutlet weak var rideLengthPrice: UILabel!

@IBOutlet weak var ridePriceTotal: UILabel!

override func viewDidLoad() {

ridePriceTotal.text = price

driverGenderPrice.text = category?.description()

rideLengthPrice.text = length?.description()

}

}



The errors that I get in the PriceView are:


Cannot assign value of type 'Float?' to type 'String?'

When you report an error, you should say which line it is. For this, use the code formatting tool (<>) .


The errors that I get in the PriceView are:

Cannot assign value of type 'Float?' to type 'String?'


Because ridePriceTotal.text expect a String, not a Float

ridePriceTotal.text = price


So write:

ridePriceTotal.text = String(format: "%.2f", price)



For the first error, Where does it occur ? Please, show the exact line.

Have you defined a collection of UIButtons somewhere ? '[UIButton]!

Or have you removed it from code ? And not removed the connections in IB ?

=> Look at each button in IB, open the connection Inspector : chek if you have a connection to a UIButton collection ; if so, remove by clicking the x.

=> Do this for all buttons (it probably occurs 3 times.


Where have you defined massageLength (probably you meant messageLength)



So, show the complete code for ViewController (you have not posted everything).

@IBOutlet weak var driverGenderFemale: UIButton!
    @IBOutlet weak var eitherGender: UIButton!
    @IBOutlet weak var driverGenderMale: UIButton!
    @IBOutlet weak var sixtyMinuteRide: UIButton!
    @IBOutlet weak var ninetyMinuteRide: UIButton!
    @IBOutlet weak var twoHourRide: UIButton!
    @IBOutlet weak var rideDatePicker: UIDatePicker!
    @IBOutlet weak var segueToPrice: UIButton!
    let array:[String] = ["cheap", "basic", "luxury", "platinum"]

  //Populate Collection View
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
        cell.rideHorizontalSwipe.image = UIImage(named: array[indexPath.row] + ".JPG")
        return cell
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        var ridePrice : Float = 0.0
        
        switch rideType {
        case .cheap :
            switch rideLength {
            case .sixtyMinuteRide : ridePrice = 5.0
            case .ninetyMinuteRide : ridePrice = 10.0
            case .twoHourRide : ridePrice = 15.0
            }
        case .basic :
            switch rideLength{
            case .sixtyMinuteRide : ridePrice = 12.5
            case .ninetyMinuteRide : ridePrice = 25.0
            case .twoHourRide : ridePrice = 40.0
            }
        case .Luxury:
            switch rideLength{
            case .sixtyMinuteRide : ridePrice = 12.0
            case .ninetyMinuteRide : ridePrice = 12.0
            case .twoHourRide : ridePrice = 12.0
            }
        case .platinum:
            switch rideLength{
            case .sixtyMinuteRide : ridePrice = 12.0
            case .ninetyMinuteRide : ridePrice = 12.0
            case .twoHourRide : ridePrice = 12.0
            }
        }
        
        if segue.identifier == "SegueToPrice" {          // The identifier you have given to the segue
            if let destVC = segue.destination as? PriceView {
                destVC.price = ridePrice
                destVC.category = rideType
                destVC.length = rideLength
            }
        }
    }

Expected member name or constructor call after type name: lines 23, 29, 35, 41

Pattern cannot match values of type 'rideLength.Type': lines 24, 30, 36, 42

Pattern cannot match values of type '[UIButton]!': lines 25, 26, 27, 31, 32, 33, 37, 38, 39, 43, 44, 45

Cannot assign value of type 'massageLength.Type' to type 'rideLength?': line 52

Cannot assign value of type '[UIButton]!' to type 'rideLength?': line 53


I didn't mean to type "massageLength" I meant "rideLegth"

Well, but I would need to see where you declare the TypesOfRide and LengthOfRide


Really, if I could see the entire project, that would be much easier to understand.


Do you mind posting your mail for a moment here, so that we can exchange by mail.

write your mail as

me @ something dot com

I'm sorry I don't feel comfortable posting my email or sending the project. I did declare the lengthOfRide:


@IBOutlet weak var sixtyMinuteRide: UIButton!  
    @IBOutlet weak var ninetyMinuteRide: UIButton!  
    @IBOutlet weak var twoHourRide: UIButton!


As for the typeOfRide: I declared it in the array because I am using the collectionView, and I have it set up so that the array populates the collectionView.


let array:[String] = ["cheap", "basic", "luxury", "platinum"]  
 

Is there a way to declare the buttons in the collectionView while maintaining the array to populate the view. This is the code I have to populate the collectionView:


  //Populate Collection View  
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {  
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell  
        cell.rideHorizontalSwipe.image = UIImage(named: array[indexPath.row] + ".JPG")  
        return cell  
    } 


This is the only code that I left out before which is the code that I moved outside of the viewController.


var rideType: rideHorizontalSwipe = .cheap
 var rideLength2: massageLength = .sixtyMinuteRide

enum rideHorizontalSwipe{
    case cheap
    case basic
    case luxury
    case platinum
    func description() -> String{
        switch self{
        case .cheap: return "cheap"
        case .basic: return "basic"
        case .luxury: return "luxury"
        case .platinum: return "platinum"
        }
    }
    
}

enum rideLength{
    case sixtyMinuteRide
    case ninetyMinuteRide
    case twoHourRide
    func description() -> String{
        switch self{
        case .sixtyMinuteRide : return "sixtyMinuteRide"
        case .ninetyMinuteRide : return "ninetyMinuteRide"
        case .twoHourRide: return "twoHourRide"
        }
    }
    
}

Sorry, I'm a bit lost in your code, I do not see where each piece falls.

In addition, seeing Types names that start with lowerCase like instan,ces is very disturbing.


1. enum declaration should be outside class definition, for global scope visibility. That's OK


2. But var declaration

var rideType: rideHorizontalSwipe = .cheap
var rideLength2: massageLength = .sixtyMinuteRide

must be in the class.


3. What is massageLength


4. You say:

As for the typeOfRide: I declared it in the array

No, you just declare labels for the types, OK, you can.

But it is probably redundant with rideHorizontalSwipe.description() ; in addition, if you use Swift 4.2, you can declare the enum as CaseIterable, which would be helpful here. We may see this later.


In collection view:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell 
        cell.rideHorizontalSwipe.image = UIImage(named: array[indexPath.row] + ".JPG") 
        return cell 
    }

How is CollectionViewCell defined ?

What is

cell.rideHorizontalSwipe here ?

Is rideHorizontalSwipe the type you have declared as enum ? Or a var in the cell definition ?????



5. So, what are the error message you get, where, once you have changed for points 2 and 3 ?

3. I meant rideLength


4. here is the code for the collectionView:


import UIKit

class CollectionViewCell: UICollectionViewCell {
    
    
    //MARK: Properties
    @IBOutlet weak var rideHorizontalSwipe: UIImageView!
}


I don't mind if it is redundant how would I make a way to declare the buttons in the collectionView while maintaining the array to populate the view, or how would I declare the enum as Caselterable.


@IBOutlet weak var driverGenderFemale: UIButton!  
    @IBOutlet weak var eitherGender: UIButton!  
    @IBOutlet weak var driverGenderMale: UIButton!  
    @IBOutlet weak var sixtyMinuteRide: UIButton!  
    @IBOutlet weak var ninetyMinuteRide: UIButton!  
    @IBOutlet weak var twoHourRide: UIButton!  
    @IBOutlet weak var rideDatePicker: UIDatePicker!  
    @IBOutlet weak var segueToPrice: UIButton!  
    let array:[String] = ["cheap", "basic", "luxury", "platinum"]  

  //Populate Collection View  
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {  
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell  
        cell.rideHorizontalSwipe.image = UIImage(named: array[indexPath.row] + ".JPG")  
        return cell  
    }  

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {  

        var ridePrice : Float = 0.0  

        switch rideType {  
        case .cheap :  
            switch rideLength {  
            case .sixtyMinuteRide : ridePrice = 5.0  
            case .ninetyMinuteRide : ridePrice = 10.0  
            case .twoHourRide : ridePrice = 15.0  
            }  
        case .basic :  
            switch rideLength{  
            case .sixtyMinuteRide : ridePrice = 12.5  
            case .ninetyMinuteRide : ridePrice = 25.0  
            case .twoHourRide : ridePrice = 40.0  
            }  
        case .Luxury:  
            switch rideLength{  
            case .sixtyMinuteRide : ridePrice = 12.0  
            case .ninetyMinuteRide : ridePrice = 12.0  
            case .twoHourRide : ridePrice = 12.0  
            }  
        case .platinum:  
            switch rideLength{  
            case .sixtyMinuteRide : ridePrice = 12.0  
            case .ninetyMinuteRide : ridePrice = 12.0  
            case .twoHourRide : ridePrice = 12.0  
            }  
        }  

        if segue.identifier == "SegueToPrice" {          // The identifier you have given to the segue  
            if let destVC = segue.destination as? PriceView {  
                destVC.price = ridePrice  
                destVC.category = rideType  
                destVC.length = rideLength  
            }  
        }  
    }

The error that I get now is:

Pattern cannot match values of type '[UIButton]!' on lines 25, 26, 27, 31, 32, 33, 37, 38, 39, 43, 44, 45

Cannot assign value of type '[UIButton]!' to type 'rideLength?' online 53


Yes, I used the rideHorizontalSwipe for the enum:


enum rideHorizontalSwipe{
    case cheap
    case basic
    case luxury
    case platinum
    func description() -> String{
        switch self{
        case .cheap: return "cheap"
        case .basic: return "basic"
        case .luxury: return "luxury"
        case .platinum: return "platinum"
        }
    }
    
}

What I still don't understand is the error with [UIButton]. And the confusion with ridePrice.


That may mean that you have defined an IBOutlet collection somewhere, we need to know where.


1. Search in your whole project for [UIButton]

It could appear as @IBOutlet: someButtons : [UIButton]!


Search also if you have used ridePrice elsewhere, for instance to define the IBOutlets


2. Using the same name for the enum Type and a var may cause unexpected problems.

Change it. And give it a name that make sense


The simplest is to change :

enum rideHorizontalSwipe{


into


enum RideType {


For future use, make it CaseIterable as well:


enum RideType: CaseIterable {


Now you can keep the name

@IBOutlet weak var rideHorizontalSwipe: UIImageView!

even if you could find something that better describe what it is.


3. To eliminate array, you can write the collection like this, thanks to CaseIterable :


   func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
        if indexPath.row < RideType.allCases.count {
             let imageName = RideType.allCases[indexPath.row].description() + ".jpg"
             cell.rideHorizontalSwipe.image = UIImage(named: imageName)
        }
        return cell
    }

The interest is to avoid in future evolutions to manage names in 2 different places (this is always errror prone).


4. In the same way, you can replace

    //Number of Views for Collection View
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return array.count
    }


with


    //Number of Views for Collection View
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return RideType.allCases.count
    }



5. Where and how have you defined rideType var and rideLength ?


So, thanks to reply precisely to all those question if you want to move on.

I changed some stuff and it now looks like this:


enum RideType{
    case cheap
    case basic
    case luxury
    case platinum
    func description() -> String{
        switch self{
        case .cheap: return "cheap"
        case .basic: return "basic"
        case .luxury: return "luxury"
        case .platinum: return "platinum"
        }
    }
    
}

enum rideLength{
    case sixtyMinuteRide
    case ninetyMinuteRide
    case twoHourRide
    func description() -> String{
        switch self{
        case .sixtyMinuteRide : return "sixtyMinuteRide"
        case .ninetyMinuteRide : return "ninetyMinuteRide"
        case .twoHourRide: return "twoHourRide"
        }
    }
    
}



    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        var ridePrice : Float = 0.0
        
        switch rideType {
        case .cheap :
            switch rideLength.self {
            case .sixtyMinuteRide : ridePrice = 90.00
            case .ninetyMinuteRide : ridePrice = 100.00
            case .twoHourRide : ridePrice = 120.00
            }
        case .basic :
            switch rideLength.self{
            case .sixtyMinuteRide : ridePrice = 12.5
            case .ninetyMinuteRide : ridePrice = 25.0
            case .twoHourRide : ridePrice = 40.0
            }
        case .luxury:
            switch rideLength.self{
            case .sixtyMinuteRide : ridePrice = 12.0
            case .ninetyMinuteRide : ridePrice = 12.0
            case .twoHourRide : ridePrice = 12.0
            }
        case .platinum:
            switch rideLength.self{
            case .sixtyMinuteRide : ridePrice = 12.0
            case .ninetyMinuteRide : ridePrice = 12.0
            case .twoHourRide : ridePrice = 12.0
            }
        }
        
        if segue.identifier == "SegueToPrice" {          // The identifier you have given to the segue
            if let destVC = segue.destination as? PriceView {
                destVC.price = ridePrice
                destVC.category = rideType
                destVC.length = rideLength
            }
        }
    }


This is the new error that I get:


Pattern cannot match values of type 'rideLength.Type'


I get this error on lines 8, 9, 10, 14, 15, 16, 20, 21, 22, 26, 27, 28

The other error that I get is this:


Cannot assign value of type 'massageLength.Type' to type 'massageLength?


on line 36

Why do you write rideLength.self ???


You should have defined a var


var rideLength : RideLength = .sixtyMinuteRide     // Just to initialize with a value


in the class // Please, name the enum type as RideLength and not rideLength


Then call in switch as:


        switch rideType {
        case .cheap :
            switch rideLength {          // NO self
            case .sixtyMinuteRide : ridePrice = 90.00
            case .ninetyMinuteRide : ridePrice = 100.00
            case .twoHourRide : ridePrice = 120.00
            }



You said you had deleted massageLength, I still see it.

This is probably due to a bad definition of rideLength, that will be corrected if you do what I propose.

Thank you for helping me it seems to be working now.

Great !


Wish you good continuation.

I'm sorry to bother you again but it's actually not working I just tried it in the simulator, and when I choose the type of ride and the duration, then I tap the continue button to go to the next screen the simulator just goes white and the simulator will disappear or the app will freeze and the simulator will disappear. Then takes me to the app delegate window in XCode, and in the debug area it tells me "terminating with uncaught exception of type NSException (lldb)". However, I am not getting any errors before I run the simulator and the build is successful.

Could you show the code in the IBAction of the button ?


And the code of the prepare for segue.


And show the PriceController, I need to see all the declarations you make there.


I suspect that you try to set up an Outlet dirctly in the prepare for segue in the originating controller, which will cause a crash.

I never put an IBAction for the button I didn't know I was supposed to. Where should I put it?


This is the code for the prepare for segue:


override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

var ridePrice : Float = 0.0

switch rideType {

case .cheap :

switch rideLength{

case .sixtyMinuteRide : ridePrice = 90.00

case .ninetyMinuteRide : ridePrice = 120.00

case .twoHourRide : ridePrice = 140.00

}

case .basic :

switch rideLength{

case .sixtyMinuteRide : ridePrice = 90.00

case .ninetyMinuteRide : ridePrice = 120.00

case .twoHourRide : ridePrice = 140.00

}

case .luxury:

switch rideLength{

case .sixtyMinuteRide : ridePrice = 90.00

case .ninetyMinuteRide : ridePrice = 120.00

case .twoHourRide : ridePrice = 140.00

}

case .platinum:

switch rideLength{

case .sixtyMinuteRide : ridePrice = 90.00

case .ninetyMinuteRide : ridePrice = 120.00

case .twoHourRide : ridePrice = 140.00

}

}

if segue.identifier == "SegueToPrice" { // The identifier you have given to the segue

if let destVC = segue.destination as? PriceView {

destVC.price = ridePrice

destVC.category = rideType

destVC.length = rideLength

}

}

}


This is the code for the PriceView:


import UIKit


class PriceView: UIViewController{

var price : Float?

var category : rideType?

var length: rideLength?

@IBOutlet weak var driverGenderPrice: UILabel!

@IBOutlet weak var rideLengthPrice: UILabel!

@IBOutlet weak var ridePriceTotal: UILabel!

override func viewDidLoad() {

ridePriceTotal.text = String(format: "%.2f", price!)

driverGenderPrice.text = category?.description()

rideLengthPrice.text = length?.description()

}

}

In-App Purchase
 
 
Q