0

This question might be very broad but I can't find any similar approaches. I have a tableView that consists of several sections. It's like a profile page where user can edit their bio i.e. name, date of birth, gender etc. As the bio is separated into different sections. How would I get all the data to be saved at once when tapped in the tableViewController and NOT the tableViewCell's.

I'm used to sending data from TableViewController to a TableViewCell to populate the cells. But I now need the opposite action where the data from TableViewCell gets sent back to the TableViewController in order for me to define what to save to the database.

Here is an example of how I would save in a normal ViewController:

func saveData() {

    guard let firstNameText = firstNameField.text, let lastNameText = lastNameField.text else { return }

    guard let uid = Auth.auth().currentUser else { return }

    let databseRef = Database.database().reference(withPath: "users/\(uid)")

    let bioItem: [String : Any] = ["firstName" : firstNameText, "lastName" : lastNameText]

    databseRef.updateChildValues(bioItem)

} 

Update

Here is what I have attempted using the solution suggested below:

protocol TextFieldCellDelegate: class {
    func textFieldCell(_ cell: FirstNameCell, didChangeText text: String)
}

class FirstNameCell: UITableViewCell {

    weak var delegate: TextFieldCellDelegate?
    var indexPath: IndexPath!

    var user: User? {
        didSet {
            guard let user = user else { return }
            nameField.text = user.firstName
        }
    }

    lazy var nameField: UITextField = {
        let field = UITextField()
        return field
    }()

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupViews()

    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


    override func setupViews() {
        addSubview(nameField)
        // Adding constraints...
    }

    @objc private func textFieldDidChange(_ textField: UITextField) {
        guard let text = nameField.text else {
            return
        }

        delegate?.textFieldCell(self, didChangeText: text)
    }

}

ViewController:

var user: User!

func textFieldCell(_ cell: FirstNameCell, didChangeText text: String) {
    switch cell.indexPath.section {
    case 0:
        if cell.indexPath.row == 0 {
            user.firstName = text

            print("New name: \(text)")
        }
    // handle other rows
    default:
        break
        // handle other sections and rows
    }
}



func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    switch (indexPath.section) {
    case 0:

        let cell = tableView.dequeueReusableCell(withIdentifier: firstNameCell, for: indexPath) as! FirstNameCell
        cell.user = self.user
        cell.delegate = self //error
        cell.indexPath = indexPath
        return cell

    case 1:
        let cell = tableView.dequeueReusableCell(withIdentifier: secondNameCell, for: indexPath) as! SecondNameCell
        cell.user = self.user
        return cell
    default:
        break
    }
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    return cell

}

1 Answer 1

1

An example project can be found on GitHub.

I've solved this a few different ways. The way I think would work best for you knowing little about your application's architecture is as follows assuming your example of a edit profile view:

Create a mutable User struct and create a local variable that will be edited each time a table row is edited.

struct User {
    var firstName: String?
    var lastName: String?
    var birthday: Date?
}

Then in your edit profile view controller:

var userModel = User()

When creating your cell subclass you need register the action UIControlEventEditingChanged as stated in this SO post and then each time the textfield is changed notify a delegate that the change happened so your view controller can receive the change event.

protocol TextFieldCellDelegate: class {
    func textFieldCell(_ cell: TextFieldCell, didChangeText text: String)
}

In the cell subclass create a variable for the delegate and create a variable for an indexPath:

weak var delegate: TextFieldCellDelegate?
var indexPath: IndexPath!

The indexPath variable will be used by the view controller to determine which cell is being modified.

Lastly in the cell subclass inside the editing changed action you hooked up earlier:

@objc private func textFieldDidChange(_ textField: UITextField) {
    guard let text = textField.text else {
        return
    }

    delegate?.textFieldCell(self, didChangeText: text)
}

In your view controller set the cell's delegate to the view controller in cell for row and set the indexPath:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //...

    cell.delegate = self
    cell.indexPath = indexPath

    //...
}

Lastly in the view controller conform to the TextFieldCellDelegate:

func textFieldCell(_ cell: TextFieldCell, didChangeText text: String) {
    switch cell.indexPath.section {
    case TableSection.user.rawValue:
        if cell.indexPath.row == 0 {
            userModel.firstName = text
        }
        // handle other rows
    default:
        // handle other sections and rows
    }
}

Now every time you edit a row in the table update the userModel. Once the user is done editing their profile and tap a save button and you call your saveData() function check each property on the userModel and see what you need to save:

// ...

var bioItemToUpdate = [String : Any]()

if let firstName = userModel.firstName {
    bioItemToUpdate["firstName"] = firstName
}

if let lastName = userModel.lastName {
    bioItemToUpdate["lastName"] = lastName
}

if let birthday = userModel.birthday {
    bioItemToUpdate["birthday"] = birthday
}

databseRef.updateChildValues(bioItemToUpdate)

Using this approach can also yield the benefit of requiring certain fields to be valid before enabling the save button.

I hope this helps you come to a good solution for your app!

Sign up to request clarification or add additional context in comments.

8 Comments

Are you able to show how you would update the userModel when a textField has been filled?
@mninety5 I added an example of what it'd look like using a table cell with a textfield.
@natualn0va Your solution is very helpful. However I'm a little confused how you conform to the TextFieldCellDelegate. It seems as though this takes in a particular cell therefore how can I affect other cells in the function? I am also unable to call cell.delegate = self as I just get an error. My app already consists of a userModel as it is used to populate the cell's in the first place. Please see my attempt with your solution.
@mninety5 you shouldn't create separate cell subclasses for each item in the user model, it'd be better to just have one cell subclass for a text field cell. Then you can setup that cell's info in the cellForRowAt in the view controller. To conform to the delegate at the top of the file where you'd see the class declaration like class ViewController: UIViewController { add a colon and the delegate name like so class ViewController: UIViewController, TextFieldCellDelegate {.
No not at all. For example if you had 3 sections that each had 2 rows and each row was a cell with a text field in it you only need 1 UITableViewCell subclass. Then in your cellForRow you can set up each as you need based on your model. I've created a small example project you can find here: github.com/naturaln0va/ProfileEdit. Hopefully this helps tie everything together!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.