skip to Main Content

I get strange bug when I’m supposed to create my ProfileViewModel. I’m creating ViewModel as @StateObject then passing it to other views via .environmentObject(). Unfortunately when I’m launching simulator I have blank black screen only when this particular ViewModel is created. When I comment the line that creates it, I get the content on the screen.

I’m creating it here:

@StateObject private var profileViewModel = ProfileViewModel()

I’m passing it to other views here:

case .profile:
    withAnimation(.linear) {
        ProfileView()
           .environmentObject(authStateManager)
           .environmentObject(tabBarStateManager)
           .environmentObject(profileViewModel)
    }

My ProfileViewModel looks like this:

import UIKit

class ProfileViewModel: ObservableObject {
    @Published var profile: Profile = Profile.demoProfile
    @Published var orders: [Order] = Order.demoOrders
    @Published var returns: [Return] = Return.demoReturns
    
    @Published var oldImage = UIImage(named: "blank_profile_image")!
    @Published var image = UIImage(named: "blank_profile_image")!
    
    @Published var shouldPresentOrderRateView: Bool = false
    @Published var shouldPresentReturnCreationView: Bool = false
    
    var datesForOrdersViewListSections: [String] {
        var ordersShortDates: [String] = []
        for order in orders {
            ordersShortDates.append(Date.getMonthNameAndYearFrom(date: order.orderDate))
        }
        return ordersShortDates.uniqued().sorted { firstDate, secondDate in
            firstDate.suffix(4) > secondDate.suffix(4)
        }
    }
    
    var datesForReturnsViewListSections: [String] {
        var returnsShortDates: [String] = []
        for userReturn in returns {
            returnsShortDates.append(Date.getMonthNameAndYearFrom(date: userReturn.returnDate))
        }
        return returnsShortDates.uniqued().sorted { firstDate, secondDate in
            firstDate.suffix(4) > secondDate.suffix(4)
        }
    }

    func uploadPhoto() {
        if !image.isEqual(oldImage) {
            oldImage = image
        }
    }
    
    func getOrdersFor(date: String) -> [Order] {
        return orders.filter {
            Date.getMonthNameAndYearFrom(date: $0.orderDate) == date
        }
    }
    
    func getReturnsFor(date: String) -> [Return] {
        return returns.filter {
            Date.getMonthNameAndYearFrom(date: $0.returnDate) == date
        }
    }
    
    func changeDefaultAddress(address: Address) {
        removeAddress(address: address)
        profile.otherAddresses.append(profile.address)
        profile.address = address
    }
    
    func removeAddress(address: Address) {
        for (index, otherAddress) in profile.otherAddresses.enumerated() {
            if otherAddress == address {
                profile.otherAddresses.remove(at: index)
                break
            }
        }
    }
    
    func editPersonalData(firstName: String = "", lastName: String = "", emailAddress: String = "") {
        if !firstName.isEmpty {
            profile.firstName = firstName
        }
        if !lastName.isEmpty {
            profile.lastName = lastName
        }
        if !emailAddress.isEmpty {
            profile.email = emailAddress
        }
    }
    
    func addNewAddress(address: Address, toBeDefault: Bool = false) {
        if toBeDefault {
            profile.otherAddresses.append(profile.address)
            profile.address = address
        } else {
            profile.otherAddresses.append(address)
        }
    }
    
    func editCardData(cardNumber: String, validThru: String, cardholderName: String) {
        if profile.creditCard != nil {
            profile.creditCard!.cardNumber = cardNumber
            profile.creditCard!.validThru = validThru
            profile.creditCard!.cardholderName = cardholderName
        }
    }
    
    func addNewCard(card: CreditCard) {
        profile.creditCard = card
    }
    
    func changeDefaultPaymentMethod(newDefaultPaymentMethod: PaymentMethod) {
        profile.defaultPaymentMethod = newDefaultPaymentMethod
    }
    
    func addUserRating(productID: String, rating: Int, review: String?) {
        profile.addRatingFor(productID: productID, rating: rating, review: review)
    }
}

To be honest, I don’t know why it occured. Until some moment everything worked fine. I made some changes in logic not connected to ViewModel and I installed KingFisher package from xcode package manager. Then I uninstalled it because I no longer needed it. Everything started right before uninstalling it. I cannot link any of my actions that could be causing it, nor have I made any changes to ProfileViewModel since then.

I have already tried:

  1. Restarting xcode and my mac
  2. Cleaning derived data folder
  3. Discarding (using GIT) any changes I made to code

Xcode console shows no output, no errors, everything builds fine

Actually I have found out now that my app do not want to show anything because of two properties of ProfileViewModel:

@Published var orders: [Order] = Order.demoOrders
@Published var returns: [Return] = Return.demoReturns

When both these structures are commented out, everything works as expected.

Structures of above mentioned:
Order

import Foundation

struct Order {
    var id: String = UUID().uuidString
    var orderDate: Date = Date()
    var estimatedDeliveryDate: Date
    var client: Profile
    var shoppingCart: Cart
    var shippingMethod: ShippingMethod
    var shippingAddress: Address
    var paymentMethod: PaymentMethod = .creditCard
    var invoice: Bool
    var totalCost: Double
    var status: OrderStatus = .placed
    
    init(client: Profile, shoppingCart: Cart, shippingMethod: ShippingMethod, shippingAddress: Address, paymentMethod: PaymentMethod = .creditCard, invoice: Bool = false) {
        self.client = client
        self.shoppingCart = shoppingCart
        self.shippingMethod = shippingMethod
        self.shippingAddress = shippingAddress
        self.paymentMethod = paymentMethod
        self.invoice = invoice
        
        self.estimatedDeliveryDate = calculateEstimatedDeliveryDate(orderDate: Date())
        
        self.totalCost = shoppingCart.products.keys.map { $0.price }.reduce(0, +)
    }
}

extension Order: Equatable, Hashable {
    static func == (lhs: Order, rhs: Order) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

extension Order: CustomStringConvertible {
    var description: String {
        "(id)nOrder Date: (Date.getDayMonthYearFrom(date: orderDate))nEstimated Delivery Date: (Date.getDayMonthYearFrom(date: estimatedDeliveryDate))nShipping Method: (shippingMethod.rawValue)nPayment Method: (paymentMethod.rawValue)nTotal Cost: (totalCost)nStatus: (status)"
    }
}

extension Order {
    static let demoOrders: [Order] = [Order(client: Profile.demoProfile,
                                            shoppingCart: Cart.demoCart,
                                            shippingMethod: .pickup,
                                            shippingAddress: Address.demoAddress),
                                      Order(client: Profile.demoProfile,
                                            shoppingCart: Cart.demoCart,
                                            shippingMethod: .parcel,
                                            shippingAddress: Address.demoAddress),
                                      Order(client: Profile.demoProfile,
                                            shoppingCart: Cart.demoCart,
                                            shippingMethod: .parcel,
                                            shippingAddress: Address.demoAddress),
                                      Order(client: Profile.demoProfile,
                                            shoppingCart: Cart.demoCart,
                                            shippingMethod: .parcel,
                                            shippingAddress: Address.demoAddress)]
}

and Return:

import Foundation

struct Return {
    var id: String = UUID().uuidString
    var returnDate: Date = Date()
    var clientID: String
    var orderID: String
    var products: [Product]
    var returnPrice: Double
    var returnMethod: ShippingMethod
    var status: ReturnStatus = .reported
    
    var bankAccountNumber: String = ""
    var bankAccountOwnerName: String = ""
    var bankAccountOwnerStreetAndHouseNumber: String = ""
    var bankAccountOwnerPostalCode: String = ""
    var bankAccountOwnerCity: String = ""
    var bankAccountOwnerCountry: String = ""
}

enum ReturnStatus: String {
    case reported = "Reported"
    case sent = "Sent"
    case delivered = "Delivered"
    case moneyReturned = "Money returned"
    case closed = "Closed"
}

extension Return: Equatable, Hashable {
    static func == (lhs: Return, rhs: Return) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

extension Return: CustomStringConvertible {
    var description: String {
        "(id)nReturn Date: (Date.getDayMonthYearFrom(date: returnDate))nClient ID: (clientID)nOrder ID: (orderID)nReturn Price: (returnPrice)nReturn Method: (returnMethod.rawValue)nStatus: (status.rawValue)"
    }
}

extension Return {
    static let demoReturns: [Return] = [Return(id: UUID().uuidString,
                                               returnDate: Date(),
                                               clientID: Profile.demoProfile.id,
                                               orderID: Order.demoOrders[0].id,
                                               products: Product.demoProducts,
                                               returnPrice: Order.demoOrders[0].totalCost,
                                               returnMethod: Order.demoOrders[0].shippingMethod,
                                               status: .reported),
                                        Return(id: UUID().uuidString,
                                               returnDate: Date(),
                                               clientID: Profile.demoProfile.id,
                                               orderID: Order.demoOrders[1].id,
                                               products: Product.demoProducts,
                                               returnPrice: Order.demoOrders[1].totalCost,
                                               returnMethod: Order.demoOrders[1].shippingMethod,
                                               status: .reported)]
}

I tried removing single properties from those structs like Profile, Cart or Products but problem remained.

2

Answers


  1. Chosen as BEST ANSWER

    It was the strangest bug I have ever seen using Swift. When I discovered the problem is connected with

    @Published var orders: [Order] = Order.demoOrders
    @Published var returns: [Return] = Return.demoReturns
    

    structures constructed in ProfileViewModel then I tried to create them from scratch starting from orders like:

        @Published var orders: [Order] = [Order(client: Profile(firstName: "",
                                                               lastName: "",
                                                               username: "",
                                                               birthDate: Date(),
                                                               email: "",
                                                               address: Address(streetName: "",
                                                                                streetNumber: "",
                                                                                apartmentNumber: "",
                                                                                zipCode: "",
                                                                                city: "",
                                                                                country: "")),
                                               shoppingCart: Cart(),
                                               shippingMethod: .courier,
                                               shippingAddress: Address(streetName: "",
                                                                        streetNumber: "",
                                                                        apartmentNumber: "",
                                                                        zipCode: "",
                                                                        city: "",
                                                                        country: ""))]
    

    And all of the sudden everything started to work, so I reverted back to:

    @Published var orders: [Order] = Order.demoOrders
    @Published var returns: [Return] = Return.demoReturns
    

    And there was no black screen anymore! Don't know exactly what might have been causing it.


  2. Found solution. It turns out that one of my functions, used to calculating estimated delivery date for demo orders was creating infinite loop at the start of the application so nothing was displayed on screen and nothing could be printed.

    func calculateEstimatedDeliveryDate(orderDate: Date) -> Date {
        var dayComponent = DateComponents()
        dayComponent.day = 2
        var estimatedDeliveryDate = Calendar.current.date(byAdding: dayComponent, to: Date())
        
        dayComponent.day = 1
        while Calendar.current.isDateInWeekend(estimatedDeliveryDate!) {
            estimatedDeliveryDate = Calendar.current.date(byAdding: dayComponent, to: Date())
        }
        
        return estimatedDeliveryDate!
    }
    

    Problem lays in this line as I was creating infinite loop by:

    estimatedDeliveryDate = Calendar.current.date(byAdding: dayComponent, to: Date())
    

    adding one day to current date and checking if it is weekend all over again. Properly done, it should be:

    estimatedDeliveryDate = Calendar.current.date(byAdding: dayComponent, to: estimatedDeliveryDate!)
    

    First time problem occured on Thursday and disappeared one day later when the calculation could be made that the app would not be stuck in infinite loop – it must have been friday as we’re adding 2 days to current date and checking if the next one is weekend. It wasn’t a weekend so the app "unlocked itself". The same case was second time as problem occured on Thursday evening and I fixed it by discovering it’s source without a necessity to wait.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search