Home > Net >  Black screen on iOS Simulator when creating ViewModel
Black screen on iOS Simulator when creating ViewModel

Time:08-09

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.

CodePudding user response:

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.

  • Related