I have an array that I'm using to create a grid with the use of a collection view. To provide the numberOfItemsInSection in the collectionView I am doing row.count * row.count to get an 8x8 grid and 64 cells. My problem is that I want to be able to access and manipulate these cells via their rows and columns, not the indexPath.row.
So if I want the 5th cell, instead of getting #4 in IndexPath.row I want to be able to do: row[0][4]. Any suggestions on how to convert IndexPath.row into a 2D array?
var row = [[Int]]()
let column: [Int]
init() {
self.column = [1, 2, 3, 4, 5, 6, 7, 8]
}
func createGrid() {
for _ in 1...8 {
row.append(column)
}
}
the blue squares/cells are the cells that I want the row and columns for
Why not simply 8 sections with 8 rows, that's what IndexPath is designed for
var grid = [[Int]]()
override func viewDidLoad() {
super.viewDidLoad()
for _ in 0..<8 {
grid.append([0,1,2,3,4,5,6,7])
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return grid.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return grid[section].count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
cell.label.text = "\(indexPath.section)/\(indexPath.row)"
return cell
}
The following should do it.
row[indexPath.row / 8][indexPath.row % 8]
Related
I have an array of 100 items called cellNames of type String. I have a collection view made up of 1 section with 100 customCollectionViewCell rows in it. I'm looking for something like this-
for cellName in cellNames {
if cellName.index == cellNames[indexpath.row] {
let cellName = cell.nameTextField
}
}
So in summation I need the cellName for index 0...100 == cellForRowAt 0...100
It looks like you are creating 100 static cells and trying to populate them with cellNames. A correct approach would be to conform to UICollectionViewDataSource and set the number of items to cellNames's count and use the indexPath provided in cellForItemAt to access each element of your array.
class ViewController: UIViewController {
let cellNames = ["1", "2", "3"] // ....
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellNames.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCellId", for: indexPath) as! CustomCell
let cellName = cellNames[indexPath.item]
cell.nameTextField.text = cellName
return cell
}
}
At the moment I'm parsing data from a Json and saving it local. Then I fill up a CollectionView with the data from the first Array. But now I want to add a second CollectionView that depends on the selection from the first CollectionView.
My Json structure:
struct Posts: Codable {
let id: Int
let name: String
let ImageURL: String
let Rarity: [PostType]
}
struct PostType: Codable {
let type: String
let test: int
let test2: String
}
What I tried:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.PostsCollectionView{
return getPostsFromDisk().count
}else
{
return feedWrapper.count //not working
}
}
var feedWrapper:[PostType] = []
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.PostsCollectionView{
let cell = PostsCollectionView.dequeueReusableCell(withReuseIdentifier: "PostsUICollectionViewCell", for: indexPath) as! PostsUICollectionViewCell
cell.PostsNameLbl.text = getPostsFromDisk()[indexPath.row].name
cell.PostsImage.sd_setImage(with: URL(string: getPostsFromDisk()[indexPath.row].ImageURL))
return cell
}else
{
let cell = PostsRareCollectionView.dequeueReusableCell(withReuseIdentifier: "PostsRareCollectionViewCell", for: indexPath) as! PostsRareCollectionViewCell
if(feedWrapper[indexPath.row].type == "normal"){
cell.PostsRareImageView.image = [imageFromAssets]
}
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
PostsName.text = getPostsFromDisk()[indexPath.row].name
//thougt i could save the selecte array to feedWrapper but its not working like expected
feedWrapper = getPostsFromDisk()[indexPath.row].Rarity
}
I think my var feedWrapper isn't filled up at the beginning but I'm not sure how to do this
I hope someone can help me with this
Thanks in advance
You need to reload the collectionView. You can use this code:
Declare your variable with didset (Assumed that your array is inside view controller)
var feedWrapper:[PostType] = [] {
didSet {
self.yourCollectionView.reloadData()
}
}
So I have 2 arrays, 1 being a photoPost array and the other a videoPost array. Now is it possible to display both these arrays in the one collection view at the same time? I tried to use the method below but does not work out. I'm not sure if I'm even doing it right.Much help would be appreciated. Thanks in advance
var photoPosts = [photoPost]()
var videoPosts = [videoPost]()
func retrieveData(){
let ref = Database.database().reference().child("videoPost").child(uid)
ref.observe(.childAdded, with: { (snapshot) in
let dictionary = videoPost(snapshot: snapshot)
self.videoPosts.append(dictionary)
})
let ref = Database.database().reference().child(“photoPost").child(uid)
ref.observe(.childAdded, with: { (snapshot) in
let dictionary = photoPost(snapshot: snapshot)
self. photoPosts.append(dictionary)
})
self.newsfeedCollectionView?.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return videoPosts.count + photoPosts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! newsfeedCollectionViewCell
cell. photoPost = photoPosts[indexPath.item]
cell.videoPost = videoPosts[indexPath.item] // fatal error:index out of range
return cell
}
Please Try this
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! newsfeedCollectionViewCell
if indexPath.item < photoPosts.count {
cell. photoPost = photoPosts[indexPath.item]
} else {
cell.videoPost = videoPosts[indexPath.item-photoPosts.count]
}
return cell
}
I'm trying to display my data as this image.
My problem is that data displayed inside table view rows are all the same thing, while it should display all data of the array.
This is the code I used to display the collectionView inside the tableView:
var onlineNews = ["local", "Economy", "Variety", "international", "sport"]
var storedOffsets = [Int: CGFloat]()
var tableIndexPath: IndexPath!
#IBOutlet var listTableView: UITableView!
var tableIndex: Int = 0
var categoryResults = [JSON]() {
didSet {
listTableView.reloadData()
}
}
let requestManager = RequestManager()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
for i in onlineNews {
requestManager.categoryList(sectionName: i)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return onlineNews.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath) as! NewsTableViewCell
tableIndex = indexPath.row
return cell
}
func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
guard let tableViewCell = cell as? NewsTableViewCell else { return }
tableViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: self, forRow: (indexPath as NSIndexPath).row)
tableViewCell.collectionViewOffset = storedOffsets[(indexPath as NSIndexPath).row] ?? 0
}
func tableView(_ tableView: UITableView,
didEndDisplaying cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
guard let tableViewCell = cell as? NewsTableViewCell else { return }
storedOffsets[(indexPath as NSIndexPath).row] = tableViewCell.collectionViewOffset
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return categoryResults.count
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ColCell",
for: indexPath) as! NewsCollectionViewCell
cell.contentType.text = categoryResults[indexPath.row]["ContentType"].stringValue **// This is where I get the same values for all table view rows**
cell.sectionName.text = onlineNews[tableIndex]
return cell
}
I'm sure someone can absolutely help me with this as I know that it takes only a small tweak to make it work, but not sure where.
Update:
I have followed a way that I believe should work, which is to declare the JSON array to be like this [[JSON]], and then use categoryResults[collection.tag][indexPath.item]["ContentType"].stringValue to get to the value. However, it gives me "index out of range" message. Do you have any clue how can I solve the issue?
var onlineNews = ["local", "Economy", "Variety", "international", "sport"]
var storedOffsets = [Int: CGFloat]()
#IBOutlet var listTableView: UITableView!
var tableIndex: Int = 0
var categoryResults = [[JSON]]() { // updated
didSet {
listTableView.reloadData()
}
}
let requestManager = RequestManager()
override func viewDidLoad() {
super.viewDidLoad()
requestManager.resetCategory()
updateSearchResults()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.updateSearchResults), name: NSNotification.Name(rawValue: "categoryResultsUpdated"), object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
for i in 0..<onlineNews.count {
requestManager.categoryList(sectionName: onlineNews[i])
}
}
func updateSearchResults() {
categoryResults = [requestManager.categoryResults] // updated
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return onlineNews.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath) as! NewsTableViewCell
tableIndex = indexPath.row
return cell
}
func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
guard let tableViewCell = cell as? NewsTableViewCell else { return }
tableViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: self, forRow: (indexPath as NSIndexPath).row)
tableViewCell.collectionViewOffset = storedOffsets[(indexPath as NSIndexPath).row] ?? 0
}
func tableView(_ tableView: UITableView,
didEndDisplaying cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
guard let tableViewCell = cell as? NewsTableViewCell else { return }
storedOffsets[(indexPath as NSIndexPath).row] = tableViewCell.collectionViewOffset
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return categoryResults[collectionView.tag].count // updated
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ColCell",
for: indexPath) as! NewsCollectionViewCell
cell.contentType.text = categoryResults[collectionView.tag][indexPath.row]["ContentType"].stringValue // updated
return cell
}
This the content of RequestManager class (where I call the API):
var categoryResults = [JSON]()
func categoryList(sectionName: String) {
let url = "http://mobile.example.com/api/Content?MobileRequest=GetCategoryNews&PageNo=1&RowsPerPage=10&Category=\(sectionName)&IssueID=0&Type=online"
print("this is the url \(url)")
Alamofire.request(url, method: .get).responseJSON{ response in
if let results = response.result.value as? [String:AnyObject] {
let items = JSON(results["Data"]?["OnlineCategoryNews"]! as Any).arrayValue
self.categoryResults += items
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "categoryResultsUpdated"), object: nil)
}
}
}
func resetCategory() {
categoryResults = []
}
deinit {
NotificationCenter.default.removeObserver(self)
}
Update 2:
And here is the method where the collectionView.tag is assigned. This is added to the tableViewCell class:
func setCollectionViewDataSourceDelegate
<D: protocol<UICollectionViewDataSource, UICollectionViewDelegate>>
(dataSourceDelegate: D, forRow row: Int) {
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.tag = row
collectionView.bounds.size.width = self.bounds.size.width
collectionView.reloadData()
}
Collection view delegate methods don't know context of their collection view. You should calculate onlineNews index depending on the collectionView instance instead of using indexPath.row, which is internal collection view index path.
Edit: better option (to avoid scrolling and layout issues) is to use single collection view, where cells are grouped in rows. If you don't want to make layout manager, you can achieve such layout by adding small, but very wide separator views between sections
Edit2:
cell.contentType.text = categoryResults[indexPath.row]["ContentType"].stringValue
uses local indexPath of this collection view. You could assign tag to tableViewCell.collectionView with a value of desired categoryResults index. Then you can use this tag as in categoryResults[collectionView.tag]
I'm trying to send multiple rows from a UITableView to another UITableView using prepareForSegue function.
When I just send one option to the 2nd UITableView, the app works perfectly, BUT when I'm choose multiple rows and send those to the second UITableView as a header (for example, if I click first in "a" row, then "d" row and then "c" row... the second UITableView just show me the row "c" as a header, not the other 2 rows), it gives an error.
Pic of the error:
These are the lines I wrote for the first UITableView:
let array = ["a", "b", "c", "d", "e"]
#IBOutlet var initialTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
initialTableView.allowsMultipleSelection = true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segueA" {
if let destination = segue.destinationViewController as? Content {
if let selectedRows = initialTableView.indexPathsForSelectedRows {
destination.title = array[selectedRows]
}
}
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.array.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellA", forIndexPath: indexPath) as UITableViewCell
cell.textLabel!.text = array[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
let cell = tableView.cellForRowAtIndexPath(indexPath)
if (cell?.accessoryType == UITableViewCellAccessoryType.Checkmark) {
cell!.accessoryType = UITableViewCellAccessoryType.None
} else {
cell!.accessoryType = UITableViewCellAccessoryType.Checkmark
}
}
The second view contains these lines:
var contentArray:[String] = []
#IBOutlet var contentTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
contentArray.append(title!)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return contentArray.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellContent = tableView.dequeueReusableCellWithIdentifier("cellContent", forIndexPath: indexPath) as UITableViewCell
cellContent.textLabel!.text = "Second test"
return cellContent
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return contentArray[section]
}
So, if I wanna choose multiples rows and send these from the first to the second UITableView... how can I solve this error?
Thanks.
Your problem is that selectedRows is an array of NSIndexPaths. You can't use that to index into your array. You need to pass an array of Strings to your second view controller instead of just setting a single string.
Use map to select the strings from array to pass to the contentArray property of the destination viewController:
destination.contentArray = selectedRows.map { array[$0.row] }
You'll need to decide a new sensible setting for destination.title.
You'll want to remove this line from viewDidLoad:
contentArray.append(title!)