Pushing data to a UICollectionView inside a UIContainerView from a UIViewcontroller - ios

I currently have a Viewcontroller which does the heavy lifting of retrieving the data. I then need to push this data to my UICollectionView inside of a UIContainerView. I have tried pushing via a segue but I need to keep refreshing the data so I keep getting the error:
'There are unexpected subviews in the container view. Perhaps the embed segue has already fired once or a subview was added programmatically?'
I then went on to investigate delegates and made a protocol to share the data between the two classes, however how can I initiate a a reloadData function from my initial view controller?
I'm still very new to delegates but I've setup the following:
protocol ProductsViewControllerDelegate {
var catalogue : CatalogueData {get set}
}
I then get my first view controller to inherit this delegate and pass data to it. I then created a delegate instance in the collection view and at the segue I set the collectionview.delegate = self however how do I refresh the data when I pass new data to it?
UPDATE
So I believe my problem is that the data isn't passing in the first place. My setup is as follows:
View controller:
struct CatalogueData {
var data : NSArray
var numberOfRows : Int
var numberOfColumns : Int
var tileName : XibNames
}
class ProductsViewController:UIViewController, CollectionCatalogueDelegate{
internal var catalogue: CatalogueData!
override func viewDidLoad() {
super.viewDidLoad()
let catdta : CatalogueData = CatalogueData(data: ProductCodes,
numberOfRows: 2,
numberOfColumns: 4,
tileName: XibNames.ProductDisplayTall)
self.catalogue = catdta
}
}
The second collection view inside of the container view is setup like so:
protocol CollectionCatalogueDelegate {
var catalogue : CatalogueData! {get set}
}
class CollectionCatalogue: UICollectionViewController, UICollectionViewDelegateFlowLayout{
#IBOutlet var cv: UICollectionView?
var delegate : CollectionCatalogueDelegate?{
didSet{
cv?.reloadData()
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return (self.delegate?.catalogue.data.count)!
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
self.cv?.register(UINib(nibName: (self.delegate?.catalogue?.tileName.rawValue)!, bundle: nil), forCellWithReuseIdentifier: (self.delegate?.catalogue?.tileName.rawValue)!)
let c:ProductCollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: selectedXib.rawValue, for: indexPath) as! ProductCollectionCell
c.layer.borderColor = UIColor.black.cgColor
c.layer.borderWidth = 0.5
c.layer.cornerRadius = 3
let mng = (self.delegate?.catalogue?.data)![indexPath.row] as? NSManagedObject
'...........DO STUFF WITH DATA HERE ETC
}
}
And dependant on other actions I will then want to update the 'catalogue' in the first view controller and it display the results on the collection view.

Related

pass data from collection view to table view in the same view controller:

If i click on a particular cell of collection view then the data should be shown related to that cell of collection view in the table view in the same view controller (not in the other view controller)
Regardless of having one or multiple view controllers. A good practice is to have a data structure that fits your visual state. For your case I would expect to have something like
var collectionViewItems: [Any]
var tableViewItems: [Any]
But to be more concrete let's assume that we have a collection view of users where after pressing a certain user a table view should update a list of friends for that user.
A data source like the following could show that:
struct User {
let name: String
let friends: [User]
}
Now to create a structure that is more fit for your display you could have something like this:
class UserFriendsDataModel {
let allUsers: [User]
var selectedUser: User?
init(allUsers: [User]) { self.allUsers = allUsers }
}
In your view controller you would create a new instance of your data model. Probably in viewDidLoad but this all depends on how you collect your data.
For instance
class ViewController: UIViewController {
private var dataModel: UserFriendsDataModel?
override func viewDidLoad() {
super.viewDidLoad()
dataModel = .init(allUsers: [
.init(name: "Me", friends: [.init(name: "You", friends: [])])
])
}
}
Now your data source implementations can use dataModel?.allUsers for collection view and dataModel?.selectedUser?.friends for your table view.
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dataModel?.selectedUser?.friends.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = dataModel?.selectedUser?.friends[indexPath.row].name
return cell
}
}
Now all that is left is interaction. When a collection view cell is pressed you would do
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let user = dataModel?.allUsers[indexPath.row]
dataModel?.selectedUser = user
tableView?.reloadData()
}
}
note here that a new user is being selected using selectedUser = user and then a reload is triggered for table view calling tableView?.reloadData() which will force the table view to call data source methods and get the new information.
An even better approach may be to listen for changes in selected user by creating your own delegates on your data model and respond to that. But that is already out of scope for this question.
I hope this puts you on the right path.
On didSelectItemAt method of collectionView you reset the data source of tableView and reload tableView that should do the job.

Create custom collection view delegate/data source

I have a controller called MyController, which is UIViewController. Inside of the controller there's CollectionView. Let's call it myCollectionView. How would I create custom CollectionViewDelegate or DataSource such that I could use them for myCollectionView. I've already attempted to create custom delegate and data source but my collection view is just empty. I found that actually my custom delegate and data source's methods do not even get called which is why I basically see an empty collection view. What I've tried is:
final class MyController: UIViewController {
private lazy var collectionView = methodCreatesCollectionView()
private var items: [String]?
private var customDataSource: CustomDataSource?
init() {
super.init(nibName: nil, bundle: nil)
view.addSubview(collectionView)
customDataSource = CustomDataSource(parent: self)
collectionView.reloadData()
}
func fill(with items: [String]) {
self.items = items
}
func methodCreatesCollectionView() -> UICollectionView {
let cv = UICollectionView(
frame: self.view.bounds
)
cv.dataSource = customDataSource
return cv
}
}
What my custom data source looks like
private class CustomDataSource: NSObject, UICollectionViewDataSource {
weak var parent: MyController?
init(parent: MyController) {
self.parent = parent
}
func collectionView(
_: UICollectionView,
numberOfItemsInSection section: Int
) -> Int {
guard let numberOfItems = parent?.items?.count else {
return 0
}
return numberOfItems
}
func numberOfSections(
in _: UICollectionView
) -> Int {
return 1
}
}
Alright, I found my own mistake.
If anyone else is doing it like me, remember to call everything related to collection view before it gets called first time.
So you have to initialise your data source/delegate before you add the collection view into the view hierarchy because the way it gets created is lazy. So it'll be created only when it's accessed/called first time.

Transferring Data from one View to another in Swift 3

Following Situation:
I have 2 Controllers, a ViewController and a CollectionViewController. The normal ViewController is supposed to collect data from the user and when the start button is clicked, an algorithm solves the problem and returns the result. The Result is supposed to be transferred to the CollectionViewController, and based on the solution the CollectionView will be built.
Below is the code I used for the start button. As you can see, the Algorithm is called, the results are stored in several variables and now I was trying to transfer the matrixArray to my CollectionViewController (its a first test). The CollectionViewController should use the data stored in this array to present some form of tableau
#IBAction func startButton(_ sender: Any) {
...
let solution = PrimalSimplex(problem: problem, currentSolution: currentSolution)
matrixArray = solution.0
basicArray = solution.1
maxArray = solution.2
currentSolutionArray = solution.3
isOptimal = solution.4
isCyceling = solution.5
let CollectionVC = storyboard?.instantiateViewController(withIdentifier: "CollectionView") as! CollectionViewController
CollectionVC.testMatrix = matrixArray
}
So far so good, the data arrives is available in the CollectionViewController after the start button is pushed. But when I try to use the data to build the CollectionView, I get an error message.
This is the code that I used in the collectionViewController to build the CollectionView (It worked before, with static values... the problems occur when I try to use values that the algorithm returns):
class CollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var myCollectionView: UICollectionView!
// Creates an empty array for the values
var testMatrix = Array<Matrix>()
//Setup CollectionView: Table to display LPs
let reuseIdentifier = "cell"
var items = testMatrix[0] <----ERROR
// MARK: - UICollectionViewDataSource protocol
// tell the collection view how many cells to make
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
// Use the outlet in our custom class to get a reference to the UILabel in the cell
cell.myLabel.text = items[indexPath.item]
cell.backgroundColor = UIColor(red:0.94, green:0.94, blue:0.94, alpha:1.0) // make cell more visible in our example project
// Change shape of cells
cell.layer.cornerRadius = 8
return cell
}
....
The error is displayed at var items = testMatrix[0]:
Cannot use instance member 'testMatrix' within property initializer; property initializers run before 'self' is available
I can understand that Xcode has a problem here, because it can't be sure that the testMatrix has values stored.... I think that the problem. I tried to use if let/ guard statement, but that didnt solve the problem.
Any advice on how to fix it or whats actually wrong here?
Maybe there is a better way to transfer the data from the first VC to the other one?
You can't initialize property from other dependent property at the class level.
You should try to initialized in viewDidLoad.
var items: Matrix?
override func viewDidLoad() {
super.viewDidLoad()
if testMatrix.count>0{
items = testMatrix[0]
}
}

Swift UICollectionView - Add/remove data from another class

I have a main view controller executed first which looks something like below,
MainViewController
class MainViewController: UIViewController {
var collectionView: UICollectionView!
var dataSource: DataSource!
SomeAction().call() {
self.dataSource.insert(message: result!, index: 0)
}
}
DataSource of the collectionview
class DataSource: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var conversation: [messageWrapper] = []
override init() {
super.init()
}
public func insert(message: messageWrapper, index: Int) {
self.conversation.insert(message, at: index)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return conversation.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let textViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "textViewCell", for: indexPath) as! TextCollectionViewCell
let description = conversation[indexPath.row].description
textViewCell.textView.text = description
return textViewCell
}
}
So, when the MainViewController is executed there is one added to the datasource of the collectionview which works perfectly fine.
Problem
Now, I have another class which looks something like
SomeController
open class SomeController {
let dataSource: DataSource = DataSource()
public func createEvent() {
self.dataSource.insert(message: result!, index: 1)
}
}
When I add some data from the above controller, the conversation is empty which doesn't have the existing one record and throw Error: Array index out of range. I can understand that it is because I have again instantiated the DataSource.
How to add/remove data from other class?
Is it the best practice to do it?
Can I make the conversation as global variable?
The Datasource class had been re initialised with it's default nil value, you have to pass the updated class to the next controller to access its updated state.
How to add/remove data from other class?
You should use class Datasource: NSObject {
And your collection view delegates on your viewcontroller class.
pass your dataSource inside prepareForSegue
Is it the best practice to do it?
Yes
Can I make the conversation as global variable?
No, best to use models / mvc style. Data on your models, ui on your viewcontrollers.
It seems your initial count is 1 but you insert at index 1(out of index)
Use self.dataSource.insert(message: result!, index: 0) insteaded
Or use append.

Parent CollectionView returning nil after selecting Child Cell

Im attempting to set up a colletionView inside a collectionView with separation between the data sources so its easier for me to manipulate UI elements.
I have my main ViewController which sets the datasource and delegate of the "outer" collection view.
class DownloadCollectionController: UIViewController {
#IBOutlet weak var outerCollectionView: UICollectionView!
var outerDataProvider : OuterDatasourceDelegate!
var outerDelegate: OuterDatasourceDelegate!
override func viewDidLoad() {
super.viewDidLoad()
// set the datasource fot the outer collection
outerDataProvider = OuterDatasourceDelegate()
// crashes on this line when selecting a cell
outerCollectionView.dataSource = outerDataProvider
outerDelegate = OuterDatasourceDelegate()
outerCollectionView.delegate = outerDelegate
}
}
The OuterDatasourceDelegate class which has some functions to populate the cell and then the willDisplayAt function the sets the datasource and delegate for the 'inner" collection view:
class OuterDatasourceDelegate: NSObject, UICollectionViewDataSource, UICollectionViewDelegate {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return GlobalOperationStore.sharedInstance.globalPackArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// cell stuff...
return outerCell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// set the datasource and delegate for the inner collection
guard let outerViewCell = cell as? OuterCollectionCell else { return }
let dataProvider = InnerDatasourceDelegate()
let delegate = InnerDatasourceDelegate()
outerViewCell.initializeCollectionViewWithDataSource(dataProvider, delegate: delegate, forItem: indexPath.item)
outerViewCell.innerCollectionView.reloadData()
outerViewCell.innerCollectionView.layoutIfNeeded()
}
}
This InnerDataSourceDelegate class just has the same functions as above that are required to populate the cells.
And my custom cell class that has reference to the "inner" collectionView and contains the initializeCollectionViewWithDataSource function called above:
class OuterCollectionCell: UICollectionViewCell {
var collectionViewDataSource : UICollectionViewDataSource!
var collectionViewDelegate : UICollectionViewDelegate!
// define the smaller collection view that is in the cell
#IBOutlet var innerCollectionView: UICollectionView!
// set delegate and datasource for new collectionView
func initializeCollectionViewWithDataSource<D: UICollectionViewDataSource, E: UICollectionViewDelegate>(_ dataSource: D, delegate :E, forItem item: Int) {
self.collectionViewDataSource = dataSource
self.collectionViewDelegate = delegate
innerCollectionView.delegate = self.collectionViewDelegate
innerCollectionView.dataSource = self.collectionViewDataSource
innerCollectionView.tag = item
innerCollectionView.reloadData()
innerCollectionView.layoutIfNeeded()
}
It displays fine when it is first loaded:
But when i select a cell it prints as i have asked:
cell no: 0 of collection view: 2
And then i get a fatal error:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
In my ViewController class on this line:
outerCollectionView.dataSource = outerDataProvider
because outerCollectionView is nil. But i can not figure out why it is nil. Im not sure why its going through viewDidLoad again as this loaded when the ViewController now won't load again until i leave the VC and come back?
----- EDIT ------

Resources