Swift How can i take data from custom uicollectionviewcell? - ios

I have a form page like below image. I should take value from this form. I have a custom collection view from the bottom. I should take textfield value from the cell. how can I do that?
I use the picker view in those cells. Can you show a little example for me?

Question: "I should take textfield value from the cell. how can I do that?"
Answer: You shouldn't do that.
View objects are for displaying information to the user and for collecting input, not for storing data.
This is especially important for UICollectionViews and UITableViews, since both of those create and recycle cells as needed as the user scrolls.
You should design your collection view to have a data model object (typically an array of structs, or an array of arrays for data in rows and columns).
As the user enters data into your text fields and taps return, you should collect the changed values and save them to your data model. Then your data model is always up-to-date and if you need to fetch the value for a specific IndexPath you just look up the data in the model.

If you know the indexPath for your cell, its easy like:
let indexPath = IndexPath(item: index, section: 0)
let cell = collectionView.cellForItem(at: indexPath) as? GuestDataCell
let value = cell?.txtDataValue.text
EDIT after comments:
Not best practice, but if you need:
var i = 0
var numberOfCells = 4
var values: [String]!
while i < numberOfCells {
let indexPath = IndexPath(item: index, section: 0)
let cell = collectionView.cellForItem(at: indexPath) as? GuestDataCell
let value = cell?.txtDataValue.text
values.append(value)
i += 1
}
But I dont really recommend you to write like this. Read please Duncan's answer.

Related

How to get all custom tableview cell textField and textView value outside tableView

I tried to access custom tableView cell textfield from outside tableView like this :
for i in 0..<NameCounter {
let indexPath = IndexPath(row: i, section: 0)
guard let cell = sampleTableView.cellForRow(at: indexPath) as? sampleCellView else{
return
}
if let text = cell.txtName.text, !text.isEmpty {
let value = text
print("Getting NameText : \(value)")
}
if let text = cell.txtNote.text, !text.isEmpty {
let value = text
print("Getting noteText : \(value)")
}}
But the problem is above method you can only get visible cell of tableView except cell is nil. and because I guard it to avoid nil cell, I did not get all textfield value.
If I remove the guard like this :
let cell = sampleTableView.cellForRow(at: indexPath) as! sampleCellView
It will crash and got some cell is nil.
How to access all textfield value from outside tableView and got all cell (cell maynot nil)?
I have multiple tableView, and inside each tableView cell, I put txtName and txtNote. I want to detect which txtName and txtNote is being edited, so I can put in the right model.
note: txtName is textfield and txtNote is textView
You shouldn't rely on the values being taken from the cell labels, text fields etc.
Once a cell goes off-screen - it gets thrown to a pool for later reuse, and it may even get deallocated.
You should keep the view state inside some array, and then you can SAFELY get any value at any index.
If you have 1000 cells, perhaps only 10-20 will be visible at any time, and maybe 40-50 or so in the reusable cells pool. If you are at index path row 100 - obviously the cells after index path row 150 will be nil.
This return
let cell = sampleTableView.cellForRow(at: indexPath) as! sampleCellView
nil and crash for any cell that's not visible as of the fact that the table cells are reusable , you need to save the user typing for each cell to model ( tableView dataSource array ), then get the values from it
var model = [String]()
// inside cellForRowAt
cell.textView.delegate = self
cell.textView.tag = indexPath.row
}
#objc func textViewDidChange(_ tex: UITextView) {
model[tex.tag] = tex.text!
}
class VC:UIViewController,UITextViewDelegate {
As a good standard practise, there should be no need to get values from the UITableViewCells. You should instead get the values from the Model that is presented in the UITableView, based on some logic as per the need.
Further, when it is needed to access any text that was typed on UITableViewCell, then it should be updated in the associated model first and should be accessed from there.
Behind the scene UITableViewCell are re-used and only the cells currently visible will show you actual data if you access it from UITableView. Hence persisting cell's entered values to some array / dictionary for later use is the option.

swift textview and labels return empty text in UITableviewcell

Current Situation:
I have a UITableView with custom cells. In the cells are 1 label and 1 textview.
Scrolling is enabled for the UITableView.
Below the Table, I have a button to save the entries from the textview.
My Problem is:
I get only the value from the first row.
From the other rows, I get always an empty text, but only if the user has scrolled.
I don't understand why and I hope you can help me.
Here is my Code:
#objc func save()
{
for i in 0..<labels.count
{
let indexPath = IndexPath(item: i, serction: 0)
let cell = self.SearchTable.dequeueReusableCell(withIdentifier: "searchCell",
for: indexPath) as! SearchCell
print("Index: \(indexPath)")
if(cell.EditField.text = ""
{
continue;
}else{
...
}
}
}
Debugger
First Row: FirstRow
Other Rows: SecondRow
Cells in a tableview are reused, so a cell at an index path that is not visible could have values from a completely different cell. If you want to get all the cells that are currently visible on the screen, you could use tableView.visibleCells docs to get all the cell that are currently on screen and extract data from there.
Alternatively, you could choose to not implement cell reuse and make your table view static. You can do this in Interface Builder, or you could also choose to create all the cells up front and return your pre-made cells in tableView(_:cellForRowAt:). Note that a setup like this is okay for small datasets, but has terrible performance for larger sets so be aware that this might not be the best way to do things. It really depends on your situation. This method of doing things would end up looking a bit like this:
var cells = [UITableViewCell]()
override func viewDidLoad() {
// do all kinds of stuff
for field in fields { // or whatever else mechanism you use as your datasource
let cell = UITableViewCell()
// configure your cell
cells.append(cell)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return cells[indexPath.row]
}
The third and last way you might want to solve this is to add a delegate to your cells and set the view controller as the delegate. If the text changes, call the delegate with the cell's index path and the new text value. You can then store this text somewhere in the view controller and read it from there when you save rather than pulling it from the cell's textfield. Personally I would prefer this method.
Rather than iterate over the cells in the tableview, you could just get the data from the data source.
Whenever the text in the cell's text field changes you could update the data source and then use the information from there to perform your save.
You must be having some kind of data source anyway, otherwise what happens to the text when the cell scrolls off the screen and comes back on again? If you aren't storing the text somewhere then you've got nothing to populate the cell with in the table views cellForRow(... delegate method.

UICollectionView - insertItems(at: indexPath) not working

I have an array with some elements in it which my UICollectionView displays. I then go and fetch more elements and append it to my array. I want to then tell the UICollectionView that elements have been added to the datasource and to update the UI.
I tried this but it is not working:
// Add more data to my existing array
myArray.append(contentsOf: moreElements)
let indexPath = [IndexPath(row: myArray.count-1, section: 0)]
myCollectionView.insertItems(at: indexPath)
I get this error but I am not sure if I am doing something wrong.
EDIT: I do NOT want to use myCollectionView.reloadData()
Your issue is that you need an IndexPath for each item you are adding to the collection view. You add multiple objects to myArray but then you only pass one IndexPath to insertItems. And this is the cause of the error.
Try the following:
var paths = [IndexPath]()
for item in 0..<moreElements.count {
let indexPath = IndexPath(row: item + myArray.count, section: 0)
paths.append(indexPath)
}
myArray.append(contentsOf: moreElements)
myCollectionView.insertItems(at: paths)

Display Two different UITABLEVIEWCELLS outside of cellForRowAtIndexPath

I have two UITableViewCells and displaying them based on condition in
cellForRowatIndexPath. Both cells related to a creation of a post and than displaying them in UITableView.
In cellForRowAtIndexPath method i don't have any issues to use condition to display cell.
For example:
if postType[indexPath.row] == "Regular" {show this sell }
else {show another}`
This is working perfect. postType array is created during the post creation.
The issue that i have is to show a proper cell outside of cellForRowAtIndexPath method.
I have a button and when user click on it they would be going to the proper cell, just like they would click on the cell it self. However I don't know how to condition an array at proper indexPath or maybe there other way. I'm not that good because just starting out to learn swift.
I can do for a one cell but i want a condition first and than display a proper cell.
This statement works for a one cell.
#IBAction func usernameBtn_click(_ sender: AnyObject) {
let i = sender.layer.value(forKey: "index") as! IndexPath
let cell = tableView.cellForRow(at: i) as! postCell
}
In my case I have two cells. How to have a condition based on post type value in array and have a proper index inserted to check the value and than display a cell.
I have these two cells:
let cell = tableView.cellForRow(at: i) as! postCell
let cell = tableView.cellForRow(at: i) as! moreinfoCell
Any suggestion would be helpful.
You mention that the button does the same as if the user selects the cell so if you have that working just deselect "User Interaction Enable" of your button.
In interface builder:
Programmatically:
button.isUserInteractionEnabled = false
Doing that the touch will be done on the UITableViewCell and the didSelectRowAt indexPath: method will be fired.

Switch statement for the tag property of a view

I'm trying to set up a tableview that has 2 cells. Both cells have their own classes and they have their own methods to configure them. In code, when i get to the cellforrowatindexpath method, i get stuck. I can only dequeue one of the cells and call it's methods, which means the other cell won't configured. I want to configure both. Here's what i'm (currently) trying in the cellforrow method:
let cells = [tableView.viewWithTag(1), tableView.viewWithTag(2)]
for view in cells {
var reuseIdentifier = "cellID"
switch view?.tag {
case 1: // error occurs here
reuseIdentifier = "storyCell"
let storyCell1 = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! StoryCell1
storyCell1.theCategory.text = newsItem.storyCategory
storyCell1.theTitile.text = newsItem.titleText
storyCell1.firstParagraph.text = newsItem.paragraph1
case 2: // error occurs here too
reuseIdentifier = "storyCell2"
let storyCell2 = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! StoryCell2
storyCell2.configureCell(newsItem)
default:
}
in the storyboard, i have given both those cells tags of 1 and 2 respectively, hence the array at the very beginning. I have 2 problems with this approach.
I can't use it because the switch statement is throwing me an error: Expression pattern of type 'Int' cannot match values of type 'Int?'
even if it were to not have the error above, i can still only return one cell at the end of the method.
any help on my approach or a different, better way to handle this would be appreciated. Thanks!
EDIT:
Since I'm sure i've added the tag property i force unwrapped the view!.tag property and the error goes away. So, the 2nd question now remains.
I don't really get what you want to do here.
What I think you're trying to do, is to configure and return two cells in the tableView(_:cellForRowAtIndexPath:) method. If you really want to do this, you're totally doing it wrong.
The table view's data source methods asks questions. And your job is to answer those questions by returning a value. For example, numberOfSectionsInTableView(_:) asks you how many sections should there be. An example answer might be return 1, return 10 etc.
Similarly, tableView(_:cellForRowAtIndexPath:) asks
What should be the cell that should be shown in the section and row specified by the index path?
And you answer by returning a UITableViewCell. You can't return two cells because it is asking you to provide a cell to be displayed at that specific section and row. If you gave it two cells to display, how can the table view display them? It doesn't make sense! Each row in the table view can only display one cell!
Therefore, instead of giving tags to the cells, use the indexPath parameter to decide which cell to create.
Let's say you want the first row to display the cell with the identifier "storyCell". And you want the second row to display the cell with the identifier "storyCell2". And your table view has only one section. You can just do this:
switch indexPath.row {
case 0:
reuseIdentifier = "storyCell"
let storyCell1 = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! StoryCell1
storyCell1.theCategory.text = newsItem.storyCategory
storyCell1.theTitile.text = newsItem.titleText
storyCell1.firstParagraph.text = newsItem.paragraph1
return storyCell1
case 1:
reuseIdentifier = "storyCell2"
let storyCell2 = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! StoryCell2
storyCell2.configureCell(newsItem)
return storyCell2
default:
// this shouldn't be reached if you do your other data source methods correctly
return UITabeViewCell()
}
And you should delete these nonsense:
let cells = [tableView.viewWithTag(1), tableView.viewWithTag(2)]
for view in cells {
var reuseIdentifier = "cellID"

Resources