By using delegates, I'm trying to pass data from a view controller to a TableView class that will display the correct number of cells. First, I needed to pass the array to that class (using a String here instead for debugging)
Inside my ViewController:
protocol GroupBillVCDelegate{
func passFriendArray(string: String)
}
...
class GroupBillViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var delegate:GroupBillVCDelegate? // for delegate passing message
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (tableView == self.FriendTableView){
let friendListCell = tableView.dequeueReusableCell(withIdentifier: "friendListCell") as! CategoryRow
// transfer friends array to CategoryRow
self.delegate?.passFriendArray(string: "Hello There")
return friendListCell
}
}
}
Inside the TableViewCell class:
class CategoryRow: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, GroupBillVCDelegate {
var s:String = String()
func passFriendArray(string: String) {
s = string
print(string)
}
...
// used for horizontal scrolling
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
passFriendArray(string: s) // message supposed to appear here
print ("TEST")
return 10
}
...
}
I have trouble setting up the delegate. When I run, the console does not display the message. Why is it not being passed?
Think of a delegate as an intercom on your desk that can be connected to your assistant's office.
This line:
var delegate:GroupBillVCDelegate? // for delegate passing message
Defines the intercom, but doesn't connect it to anything. If you press the button on the intercom, it doesn't connect to anything.
The code self.delegate?.passFriendArray(string: "Hello There")
uses "optional chaining" to send a message to the delegate if there is one, and drop the message if there is no delegate.
The thing you're missing is assigning something to the delegate:
self.delegate = someObjectThatConformsToGroupBillVCDelegateProtocol
A delegate is a one-to-one relationship.
All that being said, it doesn't really make sense to make a table view cell the delegate of a view controller. The table view has more than one cell. Which cell should be the view controller's delegate? Why that cell and not another one?
Related
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.
On the click of a create button, I navigate to tableviewcontroller screen. But nothing is populated in the tableview since the array which populates the tableview hasn't been called yet.
Now once the tableview screen is reached, after a few seconds another method is called elsewhere(in another file), which in turn calls a function in this tableviewcontroller screen. This is that method of the tableviewcontroller screen which is called...
func stopIndicator(thegrpName: String) {
stopIndicator()
let realm = try! Realm()
let chatGrp = realm.objects(ChatGroup.self)
chatGroup = chatGrp
tableview.reloadData() //CRASH HAPPENS HERE
}
In this method, once I reach tableview.reloadData() it crashes with the error Unexpectedly found nil while unwrapping an optional value..
I referred this link which seems to have a similar problem...but couldn't get much help out of it...
What could be the reason for this crash...?
EDIT 1: The numberOfRows and cellForRowAt.. is given like so...
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let chatGrp = chatGroup {
return chatGrp.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: ChatGroupTableViewCell = tableView.dequeueReusableCell(withIdentifier: "chatgroupIdentifier") as! ChatGroupTableViewCell
let groupChatObj = chatGroup![indexPath.row]
cell.chatLabel.text = groupChatObj.lastMessage?.text?.text
return cell
}
It looks you are trying to create delegate method but in another file where you are trying to call delegate method stopIndicator you are calling method on singleton instead which gives you an error.
So, set delegate right. First create protocol
protocol YourProtocol {
func stopIndicator(thegrpName: String)
}
then in another file create delegate property
var delegate: YourProtocol?
now when you need to call delegate method stopIndicator call this
delegate?.stopIndicator(thegrpName: ...)
now add to your ViewController protocol
ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, YourProtocol
and now somewhere set your another file class delegate as your ViewController (if its view set it in viewDidLoad if it is another ViewController set it in prepareForSegue)
fileClass.delegate = self
I have a Social Network Feed in form UItableView which has a cell. Now each cell has an image that animates when an even is triggered. Now, This event is in form of a string, will be triggered at every cell. the options for the event are defined in another class(of type NSObject).
My issue:
I constructed a protocol delegate method in table view, which will be called whenever the event is triggered for each cell. Then, I define this function in UITableViewCell Class, since my the image will be animating on that.
All is working well but I am unable to figure out how to assign the delegate of TableView class to cell class. What I mean is, how can I use UITableView.delegate = self in cellView class. I have tried using a static variable, but it doesn't work.
I have been playing around the protocols for a while now but really unable to figure out a solution to this.
I hope I am clear. If not, I will provide with an example in the comments. I am sorry, This is a confidential project and I cant reveal all details.
If I understand you correctly, you are trying to make each of your cells conform to a protocol that belongs to their UITableView? If this is the case then this cannot be done. The Delegation design pattern is a one to one relationship, i.e only one of your UITableViewCells would be able to conform to the UITableView's delegate.
Delegation is a simple and powerful pattern in which one object in a program acts on behalf of, or in coordination with, another object. The delegating object keeps a reference to the other object—the delegate—and at the appropriate time sends a message to it. The message informs the delegate of an event that the delegating object is about to handle or has just handled. The delegate may respond to the message by updating the appearance or state of itself or other objects in the application, and in some cases it can return a value that affects how an impending event is handled. The main value of delegation is that it allows you to easily customize the behavior of several objects in one central object.
Quote from the Apple Docs
I would suggest that your UITableViewCell should call a block (Objective-C) or a closure (Swift) whenever your specified event is triggered to achieve what you are looking for. Set up this closure in your tableView:cellForRowAtIndexPath function.
EXAMPLE
TableViewController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCellID", for: indexPath) as! MyTableViewCell
cell.eventClosure = {
//Do something once the event has been triggered.
}
return cell
}
TableViewCell
func eventTriggered()
{
//Call the closure now we have a triggered event.
eventClosure()
}
If I correctly understood your question, maybe this could help:
class ViewController: UIViewController, YourCustomTableDelegate {
#IBOutlet weak var tableView: YourCustomTableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.customTableDelegate = self
}
// table delegate method
func shouldAnimateCell(at indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.animate(...)
}
}
}
Try something like this:
Define your delegate protocol:
protocol CustomCellDelegate: class {
func animationStarted()
func animationFinished()
}
Define your CustomCell. Extremely important to define a weak delegate reference, so your classes won't retain each other.
class CustomCell: UITableViewCell {
// Don't unwrap in case the cell is enqueued!
weak var delegate: CustomCellDelegate?
/* Some initialization of the cell */
func performAnimation() {
delegate?.animationStarted()
UIView.animate(withDuration: 0.5, animations: {
/* Do some cool animation */
}) { finished in
self.delegate?.animationFinished()
}
}
}
Define your view controller. assign delegate inside tableView:cellForRowAt.
class ViewController: UITableViewDelegate, UITableViewDataSource {
/* Some view controller customization */
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: CustomCell.self)) as? CustomCell
cell.delegate = self
cell.performAnimation()
return cell
}
}
I'm trying to change the text of a UILabel in a UITableView (a property of one class,) from inside another class, but I'm having trouble. The code looks like this (The problem is at the end, in the didSelectRowAtIndexPath method of myDataSource)
The View Controller
class ViewController: UIViewController
{
//MARK: Properties
#IBOutlet weak var myTableView: UITableView!
#IBOutlet weak var myLabel: UILabel!
//needed so data from myDataSource is retained by ViewController and not just thrown away
let importedDataSource = myDataSource()
override func viewDidLoad()
{
super.viewDidLoad()
myTableView.dataSource = myDataSource()
myTableView.delegate = myDataSource()
}
override func didReceiveMemoryWarning()
{super.didReceiveMemoryWarning()}
}
The UITableViewDataSource and Delegate
class myDataSource: NSObject, UITableViewDataSource, UITableViewDelegate
{
let cellIdentifier = "myTableViewCell"
let myArray = ["Label one", "Label two", "Label three"]
//MARK: TableViewDataSource
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{return 1}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{return myArray.count}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! myTableViewCell
cell.myCellLabel.text = myArray[indexPath.row]
return cell
}
//MARK:TableView Delegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
//This Line Here returns the error
ViewController().myLabel.text = "test"
//I want the end product to be ViewController().myLabel.text = myArray[indexPath.row], but left it as "test" just to simplify and isolate the error
}
}
(there's also a UITableViewCell class called myTableViewCell, but I left it out to be shorter)
Running returns "fatal error: unexpectedly found nil while unwrapping an Optional value" upon selecting one of the rows. How should I call myLabel so as to avoid this problem? I've tried hooking it up as an #IBOutlet inside myDataSource by ctrl-dragging it from the storyboard, but as I expected, it only lets me connect it to the view controller.
sorry, the code is a bit confusing with the two similarly named labels. I'm trying to call the myLabel var created in the ViewController (first piece of code), not the myCellLabel created in myTableViewCell (not shown)
The reason you are seeing the message "fatal error: unexpectedly found nil while unwrapping an Optional value" is because you are trying to assign a value to a property of nil.
In the implementation of your UITableViewDelegate method tableView(_:didSelectRowAtIndexPath:) the statement:
ViewController().myLabel.text = "test"
creates a new instance of your ViewController class (that is what ViewController() does)
accesses the myLabel property of the new instance (recall that in your declaration of the class you defined this property to be of type UILabel!, an implicitly-unwrapped optional UILabel, and implicitly-unwrapped optionals begin life as nil until they are assigned a value)
tries to assign "test" to the text property of myLabel - but as we just noted, myLabel is nil and this is why you are receiving the error message
EDIT
In an effort to assist you in your stated goal of "trying to change the text of a UILabel" (that label being the property myLabel of your ViewController instance), the route I would recommend is:
have your ViewController class adopt the UITableViewDelegate protocol
implement your UITableViewDelegate protocol methods inside of your ViewController class, where they will have direct access to the myLabel property
once you've done this, your implementation of that delegate method (now residing inside of your ViewController class) would look like:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
myLabel.text = myTableView.dataSource.myArray[indexPath.row]
}
...and while this will work, even this can be improved - I recommend you check out Apple's Table View Programming Guide
furthermore, unless there is a compelling reason not to, I also recommend that you have your ViewController class act as your UITableViewDataSource - from the Table View Programming Guide: "[t]he class creating the table view typically makes itself the data source and delegate by adopting the UITableViewDataSource and UITableViewDelegate protocols" (see this section)
You need to get the actual table view cell that was tapped in order to access it's variables. Use cellForRowAtIndexPath. It'll look something like this:
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
if let myCell = tableView.cellForRowAtIndexPath(indexPath) as? myTableViewCell {
myCell.myLabel.text = "test"
}
}
I am using protocols and delegates to send information from my View Controller to my Custom View Controller.
ViewController.swift ==
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIAlertViewDelegate, StartAlertViewVCDelegate {
func requiredText() -> String { return "test123" }
CustomViewController.swift ==
#objc protocol StartAlertViewVCDelegate {
func requiredText() -> String }
&
func requiredText(info: NSString) { println(info) }
This works fine and dandy, but my problem is that I actually need to send an item from Core Data. This item is selected from the didSelectRowAtIndexPath.. EX:
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
println("***** Row \(indexPath.row) selected *****")
var infoData = [NSManagedObject]()
let company = infoData[indexPath.row]
company.valueForKey("openDoor") as String?
self.selectedOpenDoor = company.valueForKey("openDoor") as String!
I need to pass the selectedOpenDoor string to my Custom View Controller.
But when I run the application, the compiler grabs selectedDoorOpen before anything has been put in the string (it runs at startup)
Is there anyway to pass the delegate after didSelectRow is fired?
Any suggestions?