为什么我的数据在两个视图控制器之间不通过UITableView传输?

huangapple go评论69阅读模式
英文:

Why isn't my data not transferring between my two View Controllers using UITableViews?

问题

Based on the additional information you provided, it seems like the issue you're facing is related to the order of execution in your code and the timing of data transfer to the custom cell. Let me address your concerns and provide some suggestions:

  1. Order of Execution:
    In your RoutineTableViewCell class, you are using a lazy property for stackView, which means it gets initialized the first time it's accessed. However, the cell.randomSelectedExercises is being set in your tableView(_:cellForRowAt:) method, which might happen after the lazy property is initialized. This is why you see an empty randomSelectedExercises in your stackView.

  2. Data Transfer:
    You're trying to set randomSelectedExercises in your cell after you create the cell instance, but as you've rightly mentioned, the order of execution makes it problematic.

To address this issue, you can consider the following approach:

  • In your RoutineTableViewCell, you can add a method to set the randomSelectedExercises array explicitly. This method should be called after you have configured the cell in tableView(_:cellForRowAt:).

Here's an example of how you can modify your RoutineTableViewCell class:

class RoutineTableViewCell: UITableViewCell {
    // ... Other properties ...

    // Function to set randomSelectedExercises
    func setRandomSelectedExercises(_ exercises: [PFObject]) {
        self.randomSelectedExercises = exercises
        // After setting the data, you can update your UI elements here
        // For example, you can create your stackView elements here.
        // Make sure to reload the UI if necessary.
    }

    // ... Other code ...
}

In your tableView(_:cellForRowAt:) method:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "RoutineTableViewCell") as! RoutineTableViewCell
    // ... Configure other aspects of the cell ...

    cell.setRandomSelectedExercises(self.selectedExercises)
    return cell
}

With this approach, you ensure that randomSelectedExercises is set after the cell is created, allowing you to perform UI-related tasks in the setRandomSelectedExercises method as needed.

Please adapt this example to your specific code structure and needs. This should help you make sure your data is properly transferred to your custom cell and is available when you configure your UI elements.

英文:

I am attempting to pass data from a the UITableView function cellForRowAt to a custom UITableViewCell. The cell constructs a UIStackView with n amount of UIViews inside of it. n is a count of items in an array and is dependent on the data that is suppose to be transferred (a count of items in that array). Something very confusing happens to me here. I have checked in the VC with the tableView that the data has successfully passed by using the following snippet of code

print("SELECTED EXERCISES: ", self.selectedExercises)
cell.randomSelectedExercises = self.selectedExercises
print("PRINTING FROM CELL: ", cell.randomSelectedExercise)

I can confirm that both of these print statements return non-empty arrays. So to me, this means that the custom cell has the data I require it to have. But, when I try to print out the very same array in the UITableViewCell swift file (randomSelectedExercises) , it returns empty to me. How is this possible? From what I understand, the cell works on creating the property initializers first, then 'self' becomes available. I had a previous error telling me this and to fix it, I turned my UIStackView initializer to lazy, but this is how I ended up with my current problem.

Here is the code in beginning that is relevant to the question that pertains to the table view. I have decided to present this code incase the issue is not in my cell but in my table view code:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "RoutineTableViewCell") as! RoutineTableViewCell
        let workout = selectedWorkouts[indexPath.row]
        
        //get the information we need - just the name at this point
        let name = workout["name"] as! String
        
        
        var randomInts = [Int]()
        
        //perform a query
        
        let query = PFQuery(className: "Exercise")
        query.includeKey("associatedWorkout")
        //filter by associated workout
        query.whereKey("associatedWorkout", equalTo: workout)
        query.findObjectsInBackground{ (exercises, error) in
            if exercises != nil {

                //Created an array of random integers... this code is irrelevant to the question
                //Picking items from parent array. selectedExercises is a subarray
                for num in randomInts {
                    //allExercises just contains every possible item to pick from
                    self.selectedExercises.append(self.allExercises[num-1])
                }
                //now we have our selected workouts
                //Both print statements successfully print out correct information
                print("SELECTED EXERCISES: ", self.selectedExercises)
                cell.randomSelectedExercises = self.selectedExercises
                print("PRINTING FROM CELL: ", cell.randomSelectedExercises)

                
                
                
                //clear the arrays so we have fresh ones through each iteration
                self.selectedExercises.removeAll(keepingCapacity: false)
                self.allExercises.removeAll(keepingCapacity: false)
            } else {
                print("COULD NOT FIND WORKOUT")
            }
            
        }
        //***This works as expected - workoutName is visible in cell***
        cell.workoutName.text = name
        
        
        //clear the used arrays
        self.allExercises.removeAll(keepingCapacity: false)
        self.selectedExercises.removeAll(keepingCapacity: false)
        
        return cell
    }

Below is the code that gives me a problem in the cell swift file. the randomSelectedExercise does not have any data in it when I enter this area. This is an issue because in my for loop I am iterating from 1 to randomSelectedExercise.count. If this value is 0, I receive an error. The issue is focused in the UIStackView initializer:

import UIKit
import Parse

//Constants
let constantHeight = 50

//dynamic height number
var heightConstantConstraint: CGFloat = 10


class RoutineTableViewCell: UITableViewCell {
    
    //will hold the randomly selected exercises that need to be displayed
    //***This is where I thought the data would be saved, but it is not... Why???***
    var randomSelectedExercises = [PFObject]()
     
    static var reuseIdentifier: String {
        return String(describing: self)
    }
    
    // MARK: Overrides
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
        
    }
  
    private func commonInit() {
        contentView.addSubview(containerView)
        containerView.addSubview(workoutName)
        containerView.addSubview(stackView)

        NSLayoutConstraint.activate(staticConstraints(heightConstantConstraint: heightConstantConstraint))
        
        //reset value
        heightConstantConstraint = 10
    }
    
    //MARK: Elements
    

    //Initializing workoutName UILabel...
    let workoutName: UILabel = {...}()

    
    //***I RECEIVE AN EMPTY ARRAY IN THE PRINT STATEMENT HERE SO NUM WILL BE 0 AND I WILL RECEIVE AN ERROR IN THE FOR LOOP***
    lazy var stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.backgroundColor = .gray
        stackView.translatesAutoresizingMaskIntoConstraints = false
        

        //not capturing the data here
        print("rSE array:", randomSelectedExercises)
        var num = randomSelectedExercises.count
        for i in 1...num {
            let newView = UIView(frame: CGRect(x: 0, y: (i*50)-50, width: 100, height: constantHeight))
            heightConstantConstraint += CGFloat(constantHeight)
            newView.backgroundColor = .purple
            let newLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
            newLabel.text = "Hello World"
            newView.addSubview(newLabel)
            stackView.addSubview(newView)
        }
        
        return stackView
    }()
    
    //initializing containerView UIView ...
    let containerView: UIView = {...}()

    //Setting the constraints for each component...
    private func staticConstraints(heightConstantConstraint: CGFloat) -> [NSLayoutConstraint] {...}
        

}

Why is my data not properly transferring? How do I make my data transfer properly?


Edit

I believe I understand the problem better now thanks to @DonMag, so thank you very much. My data is not transferring properly because I am initializing my custom cell (creating the UILabel, UIStackView, randomSelectedExercise) with the default values I have given it. Then I am trying to update randomSelectedExercise afterwards, which will do me no good with my current code for stackView.

In respect to this, I have made a couple of changes and have omitted my background query as I felt it overcomplicated things for me. In my parse database, I have added the attribute associatedExercises to my Workout class. This attribute will contain an array of PFObjects of all the exercises associated with the working. At the beginning of this process, I have access to the selectedWorkouts which I iterate through when creating each cell in my table view, which is simply called workout in my cellForRowAt function. Following this, I extract the associated workouts data and put it into allExercises array. This is all I have changed since the initial question.

For full transparency, I will be pasting the entire code from the two files in question, followed with the questions I have now:

DisplayRoutineViewController.swift

import UIKit
import Parse

class DisplayRoutineViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{

    var workoutDict = [PFObject:Int]() //Int is how many of the object was picked from the previous VC
    var selectedWorkouts = [PFObject]()
    
    var allExercises = [PFObject]()
    var selectedExercises = [PFObject]()
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self

        // Do any additional setup after loading the view.
        print(workoutDict)
    }
        
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "RoutineTableViewCell") as! RoutineTableViewCell
        //cell...
        let workout = selectedWorkouts[indexPath.row]
        
        //get the information we need - just the name at this point
        let name = workout["name"] as! String
        var randomInts = [Int]()
        
        allExercises = workout["associatedExercises"] as! [PFObject] //new attribute in Prase database
        
        let loopNumber = self.workoutDict[workout]!
        print("This is for: ", name)
        let maxExercises = self.allExercises.count
        //needs to be a while loop so i have control over iterator
        var iterator = 1
        var breakFlag = false
        while iterator <= loopNumber {
            var n = Int.random(in: 1...maxExercises) //will give us numbers inclusive 1 through maxExercises
            print("Random Number = \(n)")
            
            if randomInts.isEmpty {
                //case for when randomInts is empty
                randomInts.append(n)
            } else {
                //case for when randomInt does have n in it
                for i in randomInts {
                    breakFlag = false
                    if n == i {
                        iterator = iterator - 1
                        breakFlag = true
                        break
                    }
                }
                //case for when randomInts does not have n in it
                if breakFlag == false {
                    randomInts.append(n)
                }
            }
            print("This is randomInts array: ", randomInts)
            iterator = iterator + 1
            
        }
        //at this point, we have randomInts filled with the numbers we need
        for num in randomInts {
            self.selectedExercises.append(self.allExercises[num-1]) //num-1 because indexes start at 0 and num is never 0
        }
        print("SELECTED EXERCISE HAPPENS NOW", self.selectedExercises)

        //tableView.reloadData() //maybe this will help? - no it does not just makes an infinite loop
        cell.randomSelectedExercises = self.selectedExercises
        cell.workoutName.text = name
        print("PRINTING FROM CELL: ", cell.randomSelectedExercises)
        //upload the cells stackview with the information

        //clear the arrays so we have fresh ones through each iteration
        self.selectedExercises.removeAll(keepingCapacity: false)
        self.allExercises.removeAll(keepingCapacity: false)
        
        return cell
    }
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return workoutDict.count
    }
    

}

RoutineTableViewCell.swift

import UIKit
import Parse

//Constants
let constantHeight = 50

//dynamic height number
var heightConstantConstraint: CGFloat = 10


class RoutineTableViewCell: UITableViewCell {
    
    //will hold the randomly selected exercises that need to be displayed
    var randomSelectedExercises = [PFObject]()
     
    static var reuseIdentifier: String {
        return String(describing: self)
    }
    
    // MARK: Overrides
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
        
    }
  
    private func commonInit() {
        contentView.addSubview(containerView)
        containerView.addSubview(workoutName)
        containerView.addSubview(stackView)

        NSLayoutConstraint.activate(staticConstraints(heightConstantConstraint: heightConstantConstraint))
        
        //reset value
        heightConstantConstraint = 10
    }
    
    //MARK: Elements    
    let workoutName: UILabel = {
        let label = UILabel()
        label.font = UIFont.boldSystemFont(ofSize: 16)
        label.lineBreakMode = .byTruncatingTail
        // Setting the max number of allowed lines in title to 1
        label.numberOfLines = 1
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    //added a lazy
    lazy var stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.backgroundColor = .gray
        stackView.translatesAutoresizingMaskIntoConstraints = false
        
        //make the views inside the stackview
        //not capturing the data
        print("Showing Captured Data", randomSelectedExercises)
        var num = randomSelectedExercises.count
        print("Expecting Number: ", num)
        for i in 1...num {
            //create UIViews
        }
        
        return stackView
    }()
    
    private let containerView: UIView = {
        // wrapper to contain all the subviews for the UITableViewCell
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private func staticConstraints(heightConstantConstraint: CGFloat) -> [NSLayoutConstraint] {...}

}

In my console, the print statements in lazy var stackView appear before any of the print statements in cellForRowAt because I initialize cell before these print statement. This is the issue DonMag I think was trying to point out to me but I do not understand how to switch the order of these outcomes. If I can do so, I'm guessing that information will pass to the cell correctly? Also, I have thought of doing
cell.randomSelectedExercises = self.selectedExercises before let cell = tableView.dequeueReusableCell(withIdentifier: "RoutineTableViewCell") as! RoutineTableViewCell but obviously the namespace cell has not been created yet.

Please let me know how to solve this. I am at a complete roadblock...

答案1

得分: 1

以下是您在cellForRowAt函数中正在执行的操作:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    // 获取一个单元格实例
    let cell = tableView.dequeueReusableCell(withIdentifier: "RoutineTableViewCell") as! RoutineTableViewCell

    // ...其他杂项操作

    // 启动后台进程
    query.findObjectsInBackground { (exercises, error) in
        // 此处不会立即发生任何事情...它在后台进行
    }

    // ...其他杂项操作

    return cell

    // 在此时,您已经返回了单元格
    // 并且您的后台查询正在执行其工作

}

因此,您实例化单元格,设置标签的文本,然后返回它。

该单元格创建了堆栈视图(带有空的“randomSelectedExercises”)并执行其他初始化/设置任务...

然后 - ***在后台查找对象完成后***,您设置了“randomSelectedExercises”。

您最有可能要做的是在生成“workout”对象数组时运行查询。

然后,您在`cellForRowAt`中已经拥有“random exercises”数组作为“workout”对象的一部分。

-------------

<details>
<summary>英文:</summary>

Here is what you&#39;re doing in your `cellForRowAt` func...

	func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell {
		
		// get a cell instance
		let cell = tableView.dequeueReusableCell(withIdentifier: &quot;RoutineTableViewCell&quot;) as! RoutineTableViewCell

		// ... misc stuff
		
		// start a background process
		query.findObjectsInBackground { (exercises, error) in
			// nothing here will happen yet... it happens in the background
		}
		
		// ... misc stuff
		
		return cell
		
		// at this point, you have returned the cell
		//	and your background query is doing its work

	}

So you instantiate the cell, set the text of a label, and return it.

The cell creates the stack view (with an empty `randomSelectedExercises`) and does its other init /setup tasks... 

And then - ***after your background find objects*** task completes, you set the `randomSelectedExercises`.

What you most likely want to do is run the queries while you are generating your array of &quot;workout&quot; objects.

Then you will already have your &quot;random exercises&quot; array as part of the &quot;workout&quot; object in `cellForRowAt`.

-------------

**Edit**

This is a fairly common program flow...

- at the beginning of `viewDidLoad()` 
  - setup the UI
  - set the table view&#39;s datasource and delegate
  - register the cell class
- at the end of `viewDidLoad()`
  - add a `UIActivityIndicatorView` - a &quot;spinner&quot; - probably on top of your table view
  - start the spinner animating
  - call an async / background func to retrieve your data

When the background func has finished retrieving the data:

- stop the spinner animation (hide it)
- call `.reloadData()` on the table view

You&#39;re also confusing yourself on how to create and populate the stack view (list of exercises) in your cell. Right now you&#39;re trying to &quot;populate&quot; the stack view when it&#39;s created ... but at that point, the cell has no data to do so.

Instead, when you **initialize** your cell:

- create / add / constrain subviews (title label and stack view)

in `cellForRowAt`, you would:

- assign the data to the cell and **then** populate the &quot;rows&quot; in the stack view.

It is a little tough to help you any further without a better description of what you&#39;re trying to do... What are your data structures? Are you retrieving the &quot;exercises&quot; from your Parse database? Your code shows `randomSelectedExercises` ... does that mean every time the table view is shown there will be different exercises for each workout? Or, are you setting exercises for each &quot;workout&quot; in that database? 

-----------

**Edit 2**

You&#39;re still confusing yourself about how to setup your cell and how to &quot;fill&quot; it with data.

In your cell class, you create a label and add it as a subview during init:

    let workoutName: UILabel = {
        let label = UILabel()
        label.font = UIFont.boldSystemFont(ofSize: 16)
        // etc...
        return label
    }()

    private func commonInit() {
        contentView.addSubview(containerView)
        containerView.addSubview(workoutName)
        containerView.addSubview(stackView)
        // etc...
    }

Notice that you did ***NOT*** try to set the text of the label yet -- because you have no data for the cell at that point.

So, you&#39;re sort-of doing the same thing with your stack view, **but** you&#39;re trying to ***fill it with data*** when you create it.

What you want to do is create your stack view *empty* and then populate it **when you get the data**:

    class RoutineTableViewCell: UITableViewCell {
    	var randomSelectedExercises = [PFObject]() {
    		didSet {
    			// populate your stack view here
    		}
    	}

By the way, you ***absolutely do NOT*** want to be running a query inside `cellForRowAt`. It will run every time you need a cell... ***including*** running again and again when you scroll cells in and out of view.

You need to setup your data structure so you can:

- query your db for ALL exercises
- query your db for Workouts
- loop through the Workouts and assign the random associated exercises

and **then** reload the table view. Now in `cellForRowAt` all you&#39;ll be doing is setting the `workoutName.text` and the `randomSelectedExercises`.

Now, as a side note... in this question (and your other, related questions), you&#39;ve never explained or shown us what your ultimate goal is. Based on the information you&#39;ve provided so far, I would probably tell you this would be much easier (and more flexible) if it was implemented as a multiple-section table view (where each &quot;workout&quot; is a section) rather than this approach.


</details>



huangapple
  • 本文由 发表于 2023年2月19日 09:05:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/75497380.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定