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:
- Restarting xcode and my mac
- Cleaning derived data folder
- 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
It was the strangest bug I have ever seen using Swift. When I discovered the problem is connected with
structures constructed in
ProfileViewModel
then I tried to create them from scratch starting fromorders
like:And all of the sudden everything started to work, so I reverted back to:
And there was no black screen anymore! Don't know exactly what might have been causing it.
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.
Problem lays in this line as I was creating infinite loop by:
adding one day to current date and checking if it is weekend all over again. Properly done, it should be:
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.