Inputted Values for SignUpView not saving to Backend API

47 Views Asked by At

I am currently refactoring authentication for an iOS app in SwiftUI and I am having trouble getting the data associated with a new user saved to the backend API. Essentially when the user signs up, they enter their name, email, username, password and phone number and then when they press Continue to proceed to the next screen the info they inputted gets saved.

Here is the JSON string that gets printed whenever a new user tries to sign up:

{"deviceToken":"59746886b275c31066849badc3d6981ec3e9453e5486f68174310fb30102c90c","deviceName":"iPhone","phone":"9173998955","username":"OHegazy12","password":"Password22","appVersion":"ios(17.1.1) iPhone15,3 uSTADIUM 13.5(1)","deviceID":"9CD0FA32-57F4-4052-A038-6CAAC9564318","name":"Omar Hegazy","email":"[email protected]","bundleID":"com.uSTADIUM.uSTADIUM1","platform":"ios","project":"abd87"}

Here are the errors I get:

Error: The data couldn’t be read because it is missing. Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "user", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: "user", intValue: nil) ("user").", underlyingError: nil))

Here is all the relevant code:

SignUpView()

struct SignUpView: View {
    @State private var name = ""
    @State private var email = ""
    @State private var username = ""
    @State private var password = ""
    @State private var phone = ""
    @State private var isNavigatingToInterests = false
    @ObservedObject var signupState: SignupState
    @Environment(\.presentationMode) var presentationMode
    @Environment(\.dismiss) var dismiss
    
    let progress: Double = 0.33
    
    var isContinueButtonEnabled: Bool {
        !name.isEmpty &&
        !email.isEmpty && CaseSensitivity.isValidEmail(email) &&
        !username.isEmpty && CaseSensitivity.isValidUsername(username) &&
        !password.isEmpty && CaseSensitivity.isValidPassword(password) &&
        !phone.isEmpty && CaseSensitivity.isValidPhoneNumber(phone)
    }
    
    var body: some View {
        NavigationView {
            VStack {
                Image("launch-logo")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 191, height: 45)
                    .padding(.top, -64)
                    .scaleEffect(1.5)
                ZStack {
                    VStack(alignment: .leading, spacing: 20) {
                        
                        SegmentedProgressBar(progress: progress, foregroundColor: Color(UIColor.uStadium.uPrimary))
                            .frame(height: 10)
                            .padding(.horizontal)
                            .padding(.top, -15)
                        
                        HStack {
                            Text("Personal Information")
                                .font(
                                    Font.custom("Work Sans", size: 18)
                                        .weight(.bold)
                                )
                                .foregroundColor(Color(.label))
                            
                            Spacer()
                            
                            Text("Step 1")
                                .font(Font.custom("Work Sans", size: 14))
                                .fontWeight(.bold)
                                .foregroundColor(Color(red: 0.1, green: 0.68, blue: 1))
                        }
                        .padding(.horizontal, 24)
                        
                        
                        TextFieldValidity(placeholder: "Enter your name", text: $name, isValid: true)
                        
                        TextFieldValidity(placeholder: "Enter your email", text: $email, isValid: CaseSensitivity.isValidEmail(email))
                            .autocapitalization(.none)
                        
                        TextFieldValidity(placeholder: "Enter your username", text: $username, isValid: CaseSensitivity.isValidUsername(username))
                        
                        TextFieldValidity(placeholder: "Enter your password", text: $password, isValid: CaseSensitivity.isValidPassword(password))
                        
                        TextFieldValidity(placeholder: "Enter phone number", text: $phone, isValid: CaseSensitivity.isValidPhoneNumber(phone))
                        
                        NavigationLink(destination: PickYourInterestsView(signupState: signupState), isActive: $isNavigatingToInterests) {
                            EmptyView()
                        }
                        
                        Button(action: {
                            if isContinueButtonEnabled {
                                signUp()
                                isNavigatingToInterests = true
                            }
                        }) {
                            Text("Continue")
                                .foregroundColor(.white)
                                .font(Font.custom("Work Sans", size: 18))
                                .padding()
                                .frame(maxWidth: .infinity)
                                .background(isContinueButtonEnabled ? Color.blue : Color.gray)
                                .cornerRadius(10)
                        }
                        .disabled(!isContinueButtonEnabled)
                    }
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding(.top, 60) // Adjust the top padding as needed
                    .padding(.horizontal)
                    .padding(.bottom, 40)
                    .background {
                        RoundedRectangle(cornerRadius: 16)
                            .fill(Color(red: 0.15, green: 0.17, blue: 0.22))
                            .shadow(color: .black.opacity(0.08), radius: 20, x: 0, y: 6)
                            .overlay(
                                RoundedRectangle(cornerRadius: 16)
                                    .inset(by: 0.5)
                                    .stroke(Color(red: 0.88, green: 0.88, blue: 1), lineWidth: 1)
                            )
                    }
                    .padding()
                }
            }
            .background(Color(.systemBackground))
            .navigationBarItems(leading: Button(action: {
                presentationMode.wrappedValue.dismiss()
            }, label: {
                Image(systemName: "arrow.backward.square.fill")
                    .imageScale(.large)
                    .font(.system(size: 24))
                    .foregroundColor(.gray)
                    .padding(12)
            }))
        }
        .colorScheme(.dark)
    }
}

extension SignUpView {
    func signUp() {
        let accountData = AccountSignUp2(name: name, email: email, username: username, password: password, phone: phone)
        
        AuthService.shared.signup2(data: accountData) { result, error in
            if let error = error {
                print("Error: \(error.localizedDescription)")
                debugPrint(error)
            } else if result != nil {
                print("SignUp Successful!")
            }
        }
    }
}

AuthService().signup2

func signup2(data: AccountSignUp2, _ completionHandler: @escaping (_ result: LoginOBJ?, _ error: Error?)->()) {
        switch state {
        case .loggingIn(_): return
        case .signingUp(_): return
        default: break
        }
        
        self.validUsernames = [:]
        self.validEmail = [:]
        
        setLoginCredentials(username: data.username, password: data.password)
        
        let jsonData = try! JSONEncoder().encode(data)
        if let jsonString = String(data: jsonData, encoding: .utf8) {
            print("Request JSON: \(jsonString)")
        }
        
        var request = URLRequest(url: URL(string: "\(SERVER_URL)auth/signup")!)
        request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        request.httpBody = jsonData
        
        let task = URLSession.shared.dataTask(with: request){ [unowned self, completionHandler] data, response, error in
            if let error = error {
                USTracker.tracker.trackEvent(path: .preLogout, data: ["description" : "signup_method", "event_data" : "\(error.localizedDescription)"])
                self.state = .loggedOut(error)
                completionHandler(nil, error)
                return
            }
            
            guard let data = data else {
                USTracker.tracker.trackEvent(path: .preLogout, data: ["description" : "signup_method", "event_data" : "data empty"])
                self.state = .loggedOut(error)
                completionHandler(nil, nil)
                return
            }
            
            do {
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .secondsSince1970
                
                let object = try decoder.decode(LoginOBJ.self, from: data) as LoginOBJ
                
                if let data = object.data {
                    self.checkForReferrence(userid: data.user.id)
                    self.state = .loggedIn(object)
                    completionHandler(object, nil)
                    USTracker.tracker.trackEvent(path: .signUp)
                } else {
                    self.state = .loggedOut(nil)
                    completionHandler(nil, nil)
                }
            } catch {
                Crashlytics.crashlytics().record(error:error)
                self.state = .loggedOut(error)
                completionHandler(nil, error)
            }
        }
        
        self.state = .signingUp(task)
        task.resume()
    }
/// Updated auth/signup request structure
struct AccountSignUp2: Encodable {
    let name: String
    let email: String
    let username: String
    let password: String
    let phone: String
    let project = "abd87"
    var feeds: [Int]?
    var keywords: [String]?
    var referral: AccountReferral?
    let platform = "ios"
    let deviceToken = UserDefaults.standard.object(forKey: "ustadiumDeviceToken") as? String
    let bundleID = "com.uSTADIUM.uSTADIUM1"
    let deviceName = UIDevice.current.name
    let appVersion = "ios(\(UIDevice.current.systemVersion)) \(UIDevice.current.modelName) uSTADIUM \(version)(\(build))"
    let deviceID = UIDevice.current.identifierForVendor?.uuidString ?? UserDefaults.getDeviceID()
}
struct LoginOBJ: Decodable {
    
    struct Missing: Decodable {
        var field: String?
        var reason: String?
    }
    
    struct Data: Decodable {
        var user: UserOBJ
        var missing: [LoginOBJ.Missing]
        var token: String
        var firebaseToken: String
        
        mutating func update(firebase token: String){
            self.firebaseToken = token
        }
        
        mutating func update(token: String){
            self.token = token
        }
        
        mutating func update(user: UserOBJ){
            self.user = user
        }
    }
    
    var data: LoginOBJ.Data?
    
    mutating func update(firebase token: String){
        self.data?.update(firebase: token)
    }
    
    mutating func update(token: String){
        self.data?.update(token: token)
    }
    
    mutating func update(user: UserOBJ){
        self.data?.update(user: user)
    }
}

Any and all help would be greatly appreciated! I don't know what is missing or not being read from the data.

Edit: Here is UserObj

struct UserOBJ: uCodable, Identifiable, Hashable {
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    struct Roles: uCodable {
        var id: Int
        var name: String
        var parsedAt: TimeInterval = Date().timeIntervalSince1970
        
        static var objName: String = "role"
        
        enum CodingKeys: String, CodingKey {
            case id = "id"
            case name = "name"
        }
        
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            
            parsedAt = Date().timeIntervalSince1970
            id = try values.decode(Int.self, forKey: .id)
            name = try values.decode(String.self, forKey: .name)
        }
    }
    
    var id: Int
    var nickname: String
    var username: String
    var email: String?
    var phone: String?
    var bio: String
    var city: String
    var facebook_id: String?
    var twitter_username: String?
    var twitter_id: String?
    var isVerified: Bool
    var isMegaFan: Bool
    var numPosts: Int
    var media: [MediaOBJ]?
    var feeds: [FeedOBJ]?
    var isBlocked: Bool?
    var isBanned: Bool?
    var roles: [UserOBJ.Roles]?
    var meta: MetaOBJ?
    var currentBadge: Int?
    var parsedAt: TimeInterval = Date().timeIntervalSince1970
    var deviceID: String?
    var wins: Int?
    var losses: Int?
    var takesVerified: Int
    var firstName: String?
    var lastName: String?
    var dob: String?
    var alreadyFollows: Bool?
    var roi: Int?
    
    mutating func setVerified() {
        self.takesVerified = 1
    }
    
    var isAdmin: Bool {
        get {
            if let roles = self.roles {
                return roles.count > 0
            }
            
            return false
        }
    }
    
    var phoneNo: String {
        get {
            return self.phone!
        }
        set {
            self.phone = newValue
        }
    }
    
    static var objName: String = "User"
    
    enum CodingKeys: String, CodingKey {
        case type = "type"
        case id = "id"
        case nickname = "nickname"
        case username = "username"
        case email = "email"
        case phone = "phone"
        case bio = "bio"
        case city = "city"
        case facebook_id = "facebook_id"
        case twitter_username = "twitter_username"
        case twitter_id = "twitteR_id"
        case isVerified = "isVerified"
        case isMegaFan
        case numPosts = "numPosts"
        case media = "media"
        case feeds = "feeds"
        case isBlocked = "isBlocked"
        case isBanned = "isBanned"
        case roles = "roles"
        case meta = "_meta"
        case currentBadge = "currentBadge"
        case deviceID = "deviceID"
        case wins = "wins"
        case losses = "losses"
        case takesVerified = "takesVerified"
        case firstName = "firstName"
        case lastName = "lastName"
        case dob = "dob"
        case alreadyFollows = "alreadyFollows"
        case roi = "roi"
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        
        parsedAt = Date().timeIntervalSince1970
        id = try values.decode(Int.self, forKey: .id)
        nickname = try values.decode(String.self, forKey: .nickname)
        username = try values.decode(String.self, forKey: .username)
        email = try values.decodeIfPresent(String.self, forKey: .email)
        phone = try values.decodeIfPresent(String.self, forKey: .phone)
        bio = try values.decode(String.self, forKey: .bio)
        city = try values.decode(String.self, forKey: .city)
        facebook_id = try values.decodeIfPresent(String.self, forKey: .facebook_id)
        twitter_username = try values.decodeIfPresent(String.self, forKey: .twitter_username)
        twitter_id = try values.decodeIfPresent(String.self, forKey: .twitter_id)
        isVerified = try values.decode(Bool.self, forKey: .isVerified)
        isMegaFan = try values.decode(Bool.self, forKey: .isMegaFan)
        numPosts = try values.decode(Int.self, forKey: .numPosts)
        media = try values.decodeIfPresent([MediaOBJ].self, forKey: .media)
        feeds = try values.decodeIfPresent([FeedOBJ].self, forKey: .feeds)
        isBlocked = try values.decodeIfPresent(Bool.self, forKey: .isBlocked)
        isBanned = try values.decodeIfPresent(Bool.self, forKey: .isBanned)
        roles = try values.decodeIfPresent([UserOBJ.Roles].self, forKey: .roles)
        meta = try values.decodeIfPresent(MetaOBJ.self, forKey: .meta)
        currentBadge = try values.decodeIfPresent(Int.self, forKey: .currentBadge)
        deviceID = try values.decodeIfPresent(String.self, forKey: .deviceID)
        wins = try values.decodeIfPresent(Int.self, forKey: .wins)
        losses = try values.decodeIfPresent(Int.self, forKey: .losses)
        takesVerified = try values.decode(Int.self, forKey: .takesVerified)
        firstName = try values.decodeIfPresent(String.self, forKey: .firstName)
        lastName = try values.decodeIfPresent(String.self, forKey: .lastName)
        dob = try values.decodeIfPresent(String.self, forKey: .dob)
        alreadyFollows = try values.decodeIfPresent(Bool.self, forKey: .alreadyFollows)
        roi = try values.decodeIfPresent(Int.self, forKey: .roi)
    }
    
    
    var needsVerification: Bool {
        phone == nil || takesVerified != 1 || (phone?.isEmpty ?? true) && !VerificationManager.alreadyVerified
    }
    
    /// The avatar image for this User.
    func getProfileImageURL() -> URL? {
        guard let media = media else { return nil }
        
        let avatars = media.filter { $0.displayType == .avatar }
        
        if avatars.count > 0 {
            return avatars[0].link
        }
        
        // TODO: this is not finding the image. Should fix it
        return URL(fileURLWithPath: "DefaultAvatar")
    }
    
    /// The attributed username for this user with the "@" prepended.
    func attributedUsernameWithAt() -> NSAttributedString {
        return self.getAttributedName(prepend: "@", fontSize: 14.0)
    }
    
    
    /// The attributed username for this user.
    func attributedUserame() -> NSAttributedString {
        return self.getAttributedName(prepend: "", fontSize: 14.0)
    }
    
    func attributedAt() -> NSAttributedString {
        return self.getAttributedName(prepend: "@", fontSize: 14.0)
    }
    
    func getUsername() -> String {
        "@" + username
    }
    
    func str() -> AttributedString {
        let string: AttributedString = AttributedString("@" + getUsername())
        
        if self.isVerified {
            string.attachment?.image = UIImage(named: "")
        }
        
        return string
    }
    
    @ViewBuilder
    func attributedFullAt(_ size: Int = 15, _ color: Color = .label) -> some View {
        UText(getUsername(), size: size, type: .semiBold, color: color)
            .lineLimit(1)
//        HStack(alignment: .center, spacing: .none) {
            
//            
//            if self.isVerified {
//                Image(uiImage: .verifiedBadge)
//                    .resizable()
//                    .scaledToFit()
//                    .frame(width: 8, height: 8, alignment: .center)
//            } else if self.isMegaFan {
//                Image(uiImage: .megaFanBadge)
//                    .resizable()
//                    .frame(width: 12, height: 12, alignment: .leading)
//                    .padding(.leading, -6)
//            } else if let currentBadge = currentBadge, currentBadge != 0 {
//                switch currentBadge {
//                case 1:
//                    Image(uiImage: .takesCrown)
//                        .resizable()
//                        .frame(width: 10, height: 10, alignment: .leading)
//                case 2:
//                    UText("") //("")
//                default:
//                    UText("")
//                }
//            }
//        }
    }
    
    func attributedName() -> AttributedString {
        var text = AttributedString(attributedAt().string)
        let attachment = NSTextAttachment()

        if self.isVerified {
            // create our NSTextAttachment
            text.attachment?.image = .verifiedBadge
        } else if self.isMegaFan {
            attachment.image = .megaFanBadge
        } else if let currentBadge = currentBadge, currentBadge != 0 {
            //CURRENT BADGE
            
            // create our NSTextAttachment
            switch currentBadge {
            case 1:
                attachment.image = .takesCrown
            case 2:
                text = text + AttributedString("")
            default:
                break
            }
        }
        
        
        return text
    }
    

    
    
    /// Creates the attributed name with the given prepended string, font name, size, and dictionary of other attributes.
    ///
    /// - Parameter prepend: The optional string to prepend to the resulting attributed string.
    /// - Parameter fontname: The font's name.
    /// - Parameter fontSize: The size of the font.
    /// - Parameter otherAtts: The dictionary of NSAttributedString options.
    func getAttributedName(prepend: String?, fontSize: CGFloat, otherAtts: [NSAttributedString.Key : Any]? = nil) -> NSAttributedString {
        var attrs: [NSAttributedString.Key : Any] = [:]
        
        
        if fontSize == 12.0 {
            attrs[NSAttributedString.Key.font] = UIFont.uStadium.helveticaBold(ofSize: 12)
        } else if fontSize == 14.0 {
            attrs[NSAttributedString.Key.font] = UIFont.uStadium.helveticaBold(ofSize: 14)
        } else if fontSize == 15.0 {
            attrs[NSAttributedString.Key.font] = UIFont.uStadium.helveticaBold(ofSize: 15)
        }
        
        if let otherAtts = otherAtts {
            for (k, v) in otherAtts {
                attrs.updateValue(v, forKey: k)
            }
        }
        
        var string = NSMutableAttributedString(string: " ", attributes: attrs)
        
        if self.isVerified {
            string = NSMutableAttributedString(string: "\(prepend ?? "")\(username) ", attributes: attrs)
            
            // create our NSTextAttachment
            let verifiedAttachment = NSTextAttachment()
            verifiedAttachment.image = .verifiedBadge
            
            var height: CGFloat = 12.0
            
            if fontSize <= 13.0 {
                height = 10.0;
            }
            
            let yoffset = -0.5*(fontSize)+(height/2)
            verifiedAttachment.bounds = CGRect(x: 0.0, y: yoffset, width: height, height: height)
            
            // wrap the attachment in its own attributed string so we can append it
            let verifiedString = NSAttributedString(attachment: verifiedAttachment)
            
            string.append(verifiedString)
        } else if self.isMegaFan {
            string = NSMutableAttributedString(string: "\(prepend ?? "")\(username) ", attributes: attrs)
            
            // create our NSTextAttachment
            let megaFanAttachment = NSTextAttachment()
            megaFanAttachment.image = .megaFanBadge
            
            var height: CGFloat = 12.0
            
            if fontSize <= 13.0 {
                height = 10.0;
            }
            
            let yoffset = -0.5*(fontSize)+(height/2)
            megaFanAttachment.bounds = CGRect(x: 0.0, y: yoffset, width: height, height: height)
            
            // wrap the attachment in its own attributed string so we can append it
            let megaFanString = NSAttributedString(attachment: megaFanAttachment)
            
            string.append(megaFanString)
        } else if let currentBadge = currentBadge, currentBadge != 0 {
            //CURRENT BADGE

            string = NSMutableAttributedString(string: "\(prepend ?? "")\(username) ", attributes: attrs)
            
            // create our NSTextAttachment
            let verifiedAttachment = NSTextAttachment()
            switch currentBadge {
            case 1:
                verifiedAttachment.image = .takesCrown
            default:
                break
            }
            
            
            var height: CGFloat = 12.0
            
            if fontSize <= 13.0 {
                height = 10.0;
            }
            
            let yoffset = -0.5*(fontSize)+(height/2)
            verifiedAttachment.bounds = CGRect(x: 0.0, y: yoffset, width: height, height: height)
            
            // wrap the attachment in its own attributed string so we can append it
            let verifiedString = NSAttributedString(attachment: verifiedAttachment)
            
            string.append(verifiedString)
        } else {
            string = NSMutableAttributedString(string: "\(prepend ?? "")\(username)", attributes: attrs)
        }
        
        return string
    }
    
}

extension UserOBJ: Equatable {
    static func == (lhs: UserOBJ, rhs: UserOBJ) -> Bool {
        return lhs.id == rhs.id
    }
}


class VerificationManager {
    static var alreadyVerified: Bool = false
}
0

There are 0 best solutions below