Drag and drop in collectionView with data - arrays

I'm trying to add drag and drop to my collectionView, and I have some issues;
When I drag my cell, the data disappear (UITextField). I think I don't save the data in my dataArray correctly...
And the second issue: the drag and drop "works" (without data lol) in the collectionView where I create the cell, but I can't drag this cell to another collection view. The animation is play, but I can't perform a drop.
this I my code:
fist, my class Task, my dataArray is of this type:
class Task: NSObject, Codable, NSItemProviderReading, NSItemProviderWriting {
var task: String?
var button: Bool?
init(task: String, button: Bool) {
self.task = task
self.button = button
}
static var writableTypeIdentifiersForItemProvider: [String] {
return [(kUTTypeData) as String]
}
func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: #escaping (Data?, Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: 100)
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(self)
let json = String(data: data, encoding: String.Encoding.utf8)
progress.completedUnitCount = 100
completionHandler(data, nil)
} catch {
completionHandler(nil, error)
}
return progress
}
static var readableTypeIdentifiersForItemProvider: [String] {
return [(kUTTypeData) as String]
}
static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> Self {
let decoder = JSONDecoder()
do {
let myJSON = try decoder.decode(Task.self, from: data)
return myJSON as! Self
} catch {
fatalError("Err")
}
}
Here I try to save my data and perform drag and drop:
extension LTHM_Todo: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCollectionViewCell.identifier, for: indexPath as IndexPath) as! MyCollectionViewCell
cell.myText.text = dataArray[indexPath.row].task
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 300, height: 40)
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArray.count
}
private func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCollectionViewCell.identifier, for: indexPath as IndexPath) as! MyCollectionViewCell
return cell
}
extension LTHM_Todo: UICollectionViewDragDelegate, UICollectionViewDropDelegate {
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal
{
if session.localDragSession != nil
{
if collectionView.hasActiveDrag
{
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
else
{
return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
}
}
else
{
return UICollectionViewDropProposal(operation: .forbidden)
}
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)
{
let destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath
{
destinationIndexPath = indexPath
}
else
{
let section = collectionView.numberOfSections - 1
let row = collectionView.numberOfItems(inSection: section)
destinationIndexPath = IndexPath(row: row, section: section)
}
switch coordinator.proposal.operation
{
case .move:
reorderItem(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
break
default:
return
}
}
fileprivate func reorderItem(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
let items = coordinator.items
if items.count == 1, let item = items.first, let sourceIndexPath = item.sourceIndexPath
{
var dIndexPath = destinationIndexPath
if dIndexPath.row >= collectionView.numberOfItems(inSection: 0)
{
dIndexPath.row = collectionView.numberOfItems(inSection: 0) - 1
}
collectionView.performBatchUpdates({
self.dataArray.remove(at: sourceIndexPath.row)
self.dataArray.insert(item.dragItem.localObject as! Task, at: dIndexPath.row)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [dIndexPath])
})
coordinator.drop(item.dragItem, toItemAt: dIndexPath)
}
}
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
let item = self.dataArray[indexPath.row]
let itemProvider = NSItemProvider(object: item)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}

Related

Reloading TableViewCell inside CollectionViewCell

I tried viewWillAppear, tableView.reloadData(), and collectionView.reloadData() but the data array never seems to show up
import UIKit
class yearOne: UICollectionViewController, coursesDelegate {
let customCellIdentifier = "cellID"
let customCellIdentifier2 = "cellID2"
let customCellIdentifier3 = "cellID3"
let customCellIdentifier4 = "cellID4"
let quarters = [
customLabel (title: "Fall Quarter"),
customLabel (title: "Winter Quarter"),
customLabel (title: "Spring Quarter"),
customLabel (title: "Summer Quarter")
]
func sendDataBackFall(data: String) {
fallQuarterCell.data.append(data)
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
func sendDataBackWinter(data2: String) {
winterQuarterCell.data.append(data2)
//UserDefaults.standard.set(winterQuarterCell.data, forKey: "SavedArray")
}
func sendDataBackSpring(data3: String) {
springQuarterCell.data.append(data3)
//UserDefaults.standard.set(springQuarterCell.data, forKey: "SavedArray")
}
func sendDataBackSummer(data4: String) {
summerQuarterCell.data.append(data4)
//UserDefaults.standard.set(summerQuarterCell.data, forKey: "SavedArray")
}
let vc = fallQuarterCell()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("1")
print(fallQuarterCell.data)
vc.tableView.reloadData()
collectionView.reloadData()
print("2")
print(fallQuarterCell.data)
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
self.collectionView!.register(fallQuarterCell.self, forCellWithReuseIdentifier: customCellIdentifier)
self.collectionView!.register(winterQuarterCell.self, forCellWithReuseIdentifier: customCellIdentifier2)
self.collectionView!.register(springQuarterCell.self, forCellWithReuseIdentifier: customCellIdentifier3)
self.collectionView!.register(summerQuarterCell.self, forCellWithReuseIdentifier: customCellIdentifier4)
navigationItem.title = "Year One"
navigationController?.navigationBar.prefersLargeTitles = true
collectionView?.backgroundColor = .lightGray
//navigationItem.prompt = "Click the + button to add courses, Swipe left on a course to delete."
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return quarters.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (indexPath.row == 0){
let cell1 = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier, for: indexPath) as! fallQuarterCell
cell1.layer.borderColor = UIColor.orange.cgColor
cell1.layer.borderWidth = 2
cell1.layer.cornerRadius = 5
cell1.quarters = self.quarters[0]
return cell1
}
else if (indexPath.row == 1){
let cell2 = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier2, for: indexPath) as! winterQuarterCell
cell2.layer.borderColor = UIColor.blue.cgColor
cell2.layer.borderWidth = 2
cell2.layer.cornerRadius = 5
cell2.quarters = self.quarters[1]
return cell2
}
else if (indexPath.row == 2){
let cell3 = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier3, for: indexPath) as! springQuarterCell
cell3.layer.borderColor = UIColor.green.cgColor
cell3.layer.borderWidth = 2
cell3.layer.cornerRadius = 5
cell3.quarters = self.quarters[2]
return cell3
}
else if (indexPath.row == 3){
let cell4 = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier4, for: indexPath) as! summerQuarterCell
cell4.layer.borderColor = UIColor.red.cgColor
cell4.layer.borderWidth = 2
cell4.layer.cornerRadius = 5
cell4.quarters = self.quarters[3]
return cell4
}
else{
return UICollectionViewCell()
}
}
#objc func buttonAction(sender: UIButton!) {
switch sender.tag {
case 0:
let destination = SearchPage()
destination.delegate = self
destination.tag = sender.tag
navigationController?.pushViewController(destination, animated: true)
case 1:
let destination = SearchPage()
destination.delegate = self
destination.tag = sender.tag
navigationController?.pushViewController(destination, animated: true)
case 2:
let destination = SearchPage()
destination.delegate = self
destination.tag = sender.tag
navigationController?.pushViewController(destination, animated: true)
case 3:
let destination = SearchPage()
destination.delegate = self
destination.tag = sender.tag
navigationController?.pushViewController(destination, animated: true)
default:
let destination = SearchPage()
destination.delegate = self
destination.tag = sender.tag
navigationController?.pushViewController(destination, animated: true)
}
}
}
extension yearOne : UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = (view.frame.width - 30)
return CGSize(width: width, height: 200)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 8
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
UIEdgeInsets(top: 30, left: 10, bottom: 30, right: 10)
}
}
class fallQuarterCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource {
// var data = UserDefaults.standard.object(forKey: "SavedArray") as? [String] ?? [String](){
// didSet{
// self.tableView.reloadData()
// }
// }
static var data: [String] = []
// func load(){
// if let loadeddata: [String] = UserDefaults.standard.object(forKey: "SavedArray") as? [String] {
// data = loadeddata
// tableView.reloadData()
// }
// }
let cellId = "coursesName"
let tableView:UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.backgroundColor = UIColor.white
return tableView
}()
override init(frame: CGRect){
super.init(frame: frame)
addSubview(tableView)
setupView()
}
func setupView(){
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
tableView.delegate = self
tableView.dataSource = self
self.backgroundColor = UIColor.white
contentView.addSubview(quarterLabel)
contentView.addSubview(addButton)
quarterLabel.translatesAutoresizingMaskIntoConstraints = false
quarterLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).isActive = true
quarterLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
addButton.translatesAutoresizingMaskIntoConstraints = false
addButton.topAnchor.constraint(equalTo: quarterLabel.topAnchor, constant: -5).isActive = true
addButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10).isActive = true
addButton.heightAnchor.constraint(equalToConstant: 25).isActive = true
addButton.widthAnchor.constraint(equalToConstant: 25).isActive = true
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 35).isActive = true
tableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5).isActive = true
tableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10).isActive = true
tableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fallQuarterCell.data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let stringLabel = fallQuarterCell.data[indexPath.row]
cell.textLabel?.text = stringLabel
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
if (indexPath.row == 0){
fallQuarterCell.data.remove(at: 0)
self.tableView.reloadData()
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
else if (indexPath.row == 1){
fallQuarterCell.data.remove(at: 1)
self.tableView.reloadData()
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
else if (indexPath.row == 2){
fallQuarterCell.data.remove(at: 2)
self.tableView.reloadData()
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
else if (indexPath.row == 3){
fallQuarterCell.data.remove(at: 3)
self.tableView.reloadData()
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
else if (indexPath.row == 4){
fallQuarterCell.data.remove(at: 4)
self.tableView.reloadData()
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
//add class information???
}
var quarters: customLabel? {
didSet {
guard let quarters = quarters else {return}
quarterLabel.text = quarters.title
}
}
let quarterLabel : UILabel = {
let label = UILabel()//frame: CGRect(x: 15, y: -75, width: 300, height: 50))
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.black
label.font = UIFont.boldSystemFont(ofSize: 16)
//label.textAlignment = .center
return label
}()
let addButton : UIButton = {
let button = UIButton()//frame: CGRect(x: 345, y: 10, width: 30, height: 30))
button.setImage(UIImage(named: "addicon"), for: .normal)
button.imageEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
button.tag = 0
button.addTarget(self, action: #selector(yearOne.buttonAction), for: .touchUpInside)
return button
}()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I also have the
class winterQuarterCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource
class springQuarterCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource
class summerQuarterCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource
similar to that of fallQuarterCell(). I used the print(fallQuarterCell.data) to see whether the delegation works and data string appends, and it does seem to append to the array, just that it won't appear on the fallQuarterCell tableView when backed out from the SearchPage()
Here is the 'SearchPage()where the user clicks on an element from the tableView to append to thefallQuarterCell.datathrough delegation and automatically pop backs toyearOne()`:
import UIKit
protocol coursesDelegate {
func sendDataBackFall(data: String)
func sendDataBackWinter(data2: String)
func sendDataBackSpring(data3: String)
func sendDataBackSummer(data4: String)
}
class SearchPage: UITableViewController {
var delegate: coursesDelegate?
let cellId = "course"
var allCourses : NSArray = NSArray()
var filteredCourses = [String]()
var resultSearchController = UISearchController()
var tag: Int?
#objc func addTapped(sender: UIBarButtonItem!) {
let addAlert = UIAlertController(title: "Create New Course", message: "Enter a course name", preferredStyle: .alert)
addAlert.addTextField {(textfield:UITextField) in textfield.placeholder = "Course"}
addAlert.addAction(UIAlertAction(title: "Create", style: .default, handler: { (action:UIAlertAction) in
var _customCourse = String()
if let courseTextField = addAlert.textFields?.first, let customCourse = courseTextField.text{
_customCourse = customCourse
}
}))
addAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(addAlert, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let courses : CourseList = CourseList()
allCourses = courses.coursesList
navigationItem.title = "Select Courses"
navigationController?.navigationBar.prefersLargeTitles = true
let button1 = UIBarButtonItem(title: "Add Course", style: .plain, target: self, action: #selector(addTapped))
self.navigationItem.rightBarButtonItem = button1
self.view.backgroundColor = .systemBackground
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
tableView.delegate = self
tableView.dataSource = self
self.tableView.tableFooterView = UIView()
resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.obscuresBackgroundDuringPresentation = false
controller.searchBar.placeholder = "Search Courses"
controller.searchBar.sizeToFit()
tableView.tableHeaderView = controller.searchBar
return controller
})()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (resultSearchController.isActive) {
return filteredCourses.count
}
else{
return allCourses.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
if (resultSearchController.isActive) {
cell.textLabel?.text = filteredCourses[indexPath.row]
return cell
}
else {
let courses = self.allCourses[indexPath.row]
cell.textLabel?.text = courses as? String
return cell
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
tableView.deselectRow(at: indexPath, animated: true)
if (tag == 0){
if (resultSearchController.isActive){
// fallQuarterCelll.data.append(filteredCourses[indexPath.row])
// UserDefaults.standard.set(fallQuarterCelll.data, forKey: "SavedArray")
// self.navigationController?.popViewController(animated: true)
let data = filteredCourses[indexPath.row]
delegate?.sendDataBackFall(data: data)
self.navigationController?.popViewController(animated: true)
}
else{
// fallQuarterCelll.data.append(allCourses[indexPath.row] as! String)
// UserDefaults.standard.set(fallQuarterCelll.data, forKey: "SavedArray")
// self.navigationController?.popViewController(animated: true)
let data = allCourses[indexPath.row] as! String
delegate?.sendDataBackFall(data: data)
self.navigationController?.popViewController(animated: true)
}
}
}
var isSearchBarEmpty: Bool {
return resultSearchController.searchBar.text?.isEmpty ?? true
}
}
extension SearchPage: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filteredCourses.removeAll(keepingCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (allCourses as NSArray).filtered(using: searchPredicate)
filteredCourses = array as! [String]
self.tableView.reloadData()
}
}
Ignore the UsersDefault... I'm trying to make the data appear on the fallQuarterCell tableView first.
You don't reload table view in fallQuarterCell.
let vc = fallQuarterCell() creates a new cell. Your collection view doesn't contain it, so vc.tableView.reloadData() does nothing.
When you use collectionView.dequeueReusableCell, you may get the old cell, so you should reload the table view in this cell. You should read documentation:
Call this method from your data source object when asked to provide a
new cell for the collection view. This method dequeues an existing
cell if one is available or creates a new one
For example, you can add func updateViews() in fallQuarterCell:
func updateViews() {
tableView.reloadData()
}
And call it here:
if (indexPath.row == 0){
let cell1 = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier, for: indexPath) as! fallQuarterCell
cell1.layer.borderColor = UIColor.orange.cgColor
cell1.layer.borderWidth = 2
cell1.layer.cornerRadius = 5
cell1.quarters = self.quarters[0]
cell1.updateViews()
return cell1
}
All you have to do is create a separate cell class for collection view cell, and create an outlet of your table view into that cell class.
In your cellForItemAt method of the collection view do below code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TabelCell", for: indexPath) as! MyTabelCell
cell.MyTableView.reloadData()
}
Now you have to reload collection view and it will automatically reload the table view. That's the best and easiest way to approach TableView in CollectionView

MultipleCollectionViews with two arrays in Swift

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()
}
}

URL array throwing optional error swift

I am not sure how to resolve the optional type error that occurs at "if let imageURL = imageFile.path" in my code below. The error it throws is "Initializer for conditional binding must have Optional type, not 'String'"
After googling, I'm guessing it has something to do with the directoryContentsArray = URL I set at the beginning of my CollectionViewController class.
Please help!
P.S. Sorry for the repeat optional error question, but I'm super confused :/
class CollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var directoryContentsArray = [URL]()
fileprivate let itemsPerRow: CGFloat = 3
fileprivate let sectionInsets = UIEdgeInsets(top: 50.0, left: 20.0, bottom: 50.0, right: 20.0)
#IBOutlet weak var collectionView: UICollectionView! { didSet {
collectionView.delegate = self
collectionView.dataSource = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
func fetchDirectoryContents() {
let fileManager = FileManager.default
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryContents = try! fileManager.contentsOfDirectory(at: documentDirectory, includingPropertiesForKeys: nil)
self.directoryContentsArray = directoryContents
self.collectionView.reloadData()
}
checkPhotoLibraryPermission()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.collectionView.reloadData()
}
//number of views
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return directoryContentsArray.count
}
//populate the views
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? myCell {
let imageFile = self.directoryContentsArray[indexPath.item]
if let imageURL = imageFile.path,
imageFile.pathExtension == "jpeg",
let image = UIImage(contentsOfFile: imageURL) {
cell.myImageView.image = image
} else {
fatalError("Can't create image from file \(imageFile)")
}
return cell
}
return UICollectionViewCell()
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let imageURL = info[UIImagePickerControllerImageURL] as? URL {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
do {
try FileManager.default.moveItem(at: imageURL.standardizedFileURL, to: documentDirectory.appendingPathComponent(imageURL.lastPathComponent))
collectionView.reloadData()
} catch {
print(error)
}
}
picker.dismiss(animated: true, completion: nil)
}
Thanks again!
The definition of the path property of URL is:
var path: String
So it doesn't return an optional which means you don't need to do the let assignment.
Just change to this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? myCell {
let imageFile = self.directoryContentsArray[indexPath.item]
if imageFile.pathExtension == "jpeg",
let image = UIImage(contentsOfFile: imageFile.path) {
cell.myImageView.image = image
} else {
fatalError("Can't create image from file \(imageFile)")
}
return cell
}
return UICollectionViewCell()
}

append 2 arrays in a collection view

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
}

Swift: How to Display Parsed JSON Data in CollectionView inside a TableView?

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]

Resources