I am neither an iOS developer, nor a swift developer, but please bear with me:
I am currently trying to implement a simple iOS app but I have difficulties understanding how exactly I am supposed to set up custom UIViews and ViewControllers for those UIViews.
I am using a UIScrollView that is containing items a little bit more complex than just images, thats what I use custom views for.
What I did was:
I created a .xib file, the view itself. I added some elements (here it is only a textfield, for simplicity's sake).
I created a cocoa touch class "CustomView" that inherits from UIView and set my view up to be of that class (inside the class I just set up elements and such).
Now I want a ViewController that controls the class whenever it is rendered (for example reacting to the changing textField).
I cant manage everything from my main ViewController, because it would get too big (e.g. 3 scrollViews * 5 subviews that need to be managed).
I want a solution that uses ViewControllers for each subview (in case they themselves will have subviews, too).
How do I do that?
Do I need to add some sort of childViewController?
I really am at loss, most of the blog posts and SO examples simply do not work and/or are outdated and I am unsure about whether or not I got the whole View - ViewController pattern wrong.
Let's say you have two view controllers, MainViewController and TableViewController. TableVC's main view is to be a subview of MainVC's main view. In addition, you wish to pass back to MainVC which cell was selected in TableVC.
A solution is (a) make TableVC be a child to MainVC and (b) make MainVC be a delegate for TableVC.
TableViewController:
protocol TableVCDelegate {
func cellSelected(sender: TableViewController)
}
class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// please note that you can do delegation differently,
// this way results in crashes if delegate is nil!
var delegate:TableVCDelegate! = nil
var someValue = ""
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// set someValue to contents in the selected cell or it's data source
someValue = "Hello World!"
delegate.cellSelected(sender: self)
}
}
MainViewController:
class MainViewController: UIViewController, TableVCDelegate {
let tableVC = TableViewController()
override func viewDidLoad() {
// make tableVC be a child of this VC
addChild(tableVC)
tableVC.didMove(toParent: self)
tableVC.delegate = self
// position tableVC.view
tableVC.view.translatesAutoresizingMaskIntoConstraints = false
}
func cellSelected(sender: TableViewController) {
print(sender.someValue) // this should send "Hello World!" to the console
}
}
This is obviously untested code, but it is based on product code. This is meant to be a shell to help you get started.
Related
Im new to programming and trying to build my own app, I wonder how Im I supposed to link the info I get from the addTask viewcontroller to the tableview cell? At the moment Im just trying to get the text from the textfield and Im going to add the other features later.
What Im trying to do
Refer This :-
Create a global array add remove element from that array on click of your button
You can pass data from one view controller to another using Delegates. Check my ans here. You can set your table view class as delegate of your task view controller. Implement the protocol methods of task view controller get the data and reload table.
Hope it helps.
Happyy Coding!!
You can pass data using Delegation .
In Second ViewController
import UIKit
protocol secondViewDelegate: class {
func passData(arrData : [Any])
}
class SecondViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
weak var delegate: secondViewDelegate? = nil
#IBAction func clickOnButton(_ sender: Any) {
self.delegate.passData([]) // replace your array here
}
}
In FirstViewController
class FirstViewController: UIViewController, UITableViewDataSource, secondViewDelegate
let objectSecondVC: SecondViewController? = storyboard?.instantiateViewController(withIdentifier: "secondVCID") as! SecondViewController?
objectSecondVC?.delegate = self
navigationController?.pushViewController(objectSecondVC?, animated: true)
Second ViewController Delegate Method in FirstViewController
func passData(arrData : [Any]){
// append to your main array
}
It seems like your add task view controller is connected with your table view controller though a segue. So when you moving back from add task view controller, you can use unwind to pass data back. Here is a detailed tutorial with simple instructions and pictures.
I am working on an application where I need to pass an array of values from a click of tableview cell in my tableviewcontroller which is the initial view controller to a contentview view controller with page curl transition.
Each of the page will have a textview which will be populated with value passed from the initial view controller and can be edited by the user.
My issue is, I am not able to update the array with the new value and pass it back to the initial view controller
I tried the following:
I implemented pageviewcontroller methods in my initialview controller to create instances of the ContentViewController with page curl transition and was able to pass values to each of the pages when the page curl was done. But I am trying to figure out a way to pass back the updated value to the tableviewcontroller from where I instantiated the object.
I tried Segue from tableview to the contentViewcontroller, but it does not work.
Appreciate if somebody can help me.
Use the delegate pattern.
Define a protocol within ContentViewController :
protocol UpdateModelDelegate {
func didDismissContentViewController(controller:ContentViewController)
}
Establish a delegate variable within ContentViewController :
var delegate: UpdateModelDelegate?
When you dismiss ContentViewController call delegate?.didDismissContentViewController(self) which will send the data back to your UITableViewController.
Have the UITableViewController conform to this protocol.
class MyTableViewControllerSubclass: UITableviewController, UpdateModelDelegate
When presenting the ContentViewController set your UITableViewSubclass as the delegate.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var contentViewController: ContentViewController()
//After you pass the necessary data to contentViewController...
contentViewController.delegate? = self
}
finally, implement the delegate function within your UITableviewController subclass.
func didDismissContentViewController(controller:ContentViewController) {
//You're passed in the contentViewController here, use relevant data to update your model.
}
In a detail view controller, I've a 'featureImage' in the top left, and a thin horizontal strip of images below this. The strip of images is an embedded container view managed by a custom CollectionViewController, which shows an array of images. The initial featureImage is the first image in an array of images[0], and the same array is passed to the collection view.
I'd like the featureImage to update to the same image if a cell in the container view is selected / tapped.
I guess I need to call the delegate method didSelectItemAtIndexPath, which will give me the indexPath. Right? But then how do I pass the indexPath, which is already from a delegate, back to the detail view controller.
EDITED - The code shows code overlap and differences between Responder Chain AND delegate approaches. Uncommented in the didSelectItemAtIndex path, the Responder Chain approach works, while the delegate approach does not.
Protocol defined and included at top of DetailViewController (I doesn't seem to matter which file the protocol is in, and is only typed to class to allow the delegate property to be 'weak'). Needed for both approaches.
protocol FeatureImageController: class {
func featureImageSelected(indexPath: NSIndexPath)
}
class DetailViewController: UIViewController, FeatureImageController {
Delegate property declared in the custom UICollectionViewController class. Only needed for delegate approach.
weak var delegate: FeatureImageController?
Delegate property initiated in the DetailViewController. Only needed for delegate approach.
override func viewDidLoad() {
super.viewDidLoad()
let photoCollectionVC = PhotoCollectionVC()
photoCollectionVC.delegate = self as FeatureImageController ... }
The Responder Chain (active) OR the delegate approach (commented out) within the collection view controllers didSelectItemAtIndexPath method.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
if let imageSelector = targetForAction("featureImageSelected:", withSender: self) as? FeatureImageController {
imageSelector.featureImageSelected(indexPath)
}
// self.delegate?.featureImageSelected(indexPath)
}
Delegate method in DetailViewController. Needed for both.
func featureImageSelected(indexPath: NSIndexPath) {
record?.featureImage = record?.images[indexPath.row]
self.configureView()
}
The communication of data selection between View Controllers in my experience can best be achieved in two ways- the delegation or responder chain route. Either way the first step would be creating a protocol that your DetailViewController will adhere to. Something like:
protocol FeatureImageController: class {
func featureImageSelected(image: UIImage)
}
Your DetailViewController would then implement this function and use it to change the 'feature image'. How this is communicated then depends on whether you use delegation or the responder chain.
Delegation
If you prefer to use delegation then declare a delegate property on your CollectionViewController like so:
weak var delegate: FeatureImageController?
then in didSelectItemAtIndexPath you would determine the selected image using the provided indexPath and pass it to your delegate:
delegate?.featureImageSelected(selectedImage)
where selectedImage is the image selected from the collection view.
Responder Chain
If you decide to use the responder chain then you need not declare a delegate property. Instead you would ask for the first target that responds to your protocol method. So inside didSelectItemAtIndexPath you would say:
if let imageController = targetForAction("featureImageSelected:", withSender: self) as? FeatureImageController {
imageController.featureImageSelected(selectedImage)
}
Both methods (delegation or responder chain) allow the collection view controller to pass its selection to the detail controller. The delegation route is more common in the Framework but I find as we use containers within containers more often it becomes pretty nasty to properly manage the chain of delegates without an amount of 'coupling' I'm not comfortable with. The responder chain, on the other hand, is already provided by the framework to 'dig' into the hierarchy of controllers to find one willing to handle your action.
A portion of my app has an embedded master-detail section. Each detail view is using a custom UIViewController. When I change the value of something inside one of these UIViewControllers I need to be able to grey out one of the table rows in the master UITableViewController.
The closest I have seen to a solution is to use NSNotificationCenter to bubble up any changes, though this feels a little untidy..
Another solution is to use delegates? But I haven't come across any example solutions or tutorials in how to use this in Swift?
I've also experimented just trying to access the table view by navigating back up the hierarchy:
let navController = self.splitViewController!.viewControllers[0];
navController.tableView.reloadData()
I know the example above is wrong, but I don't know how to access the master view that way, or even if it is the right approach.
Oh, I am trying to call reloadData() because in the master view there is some logic which checks the condition as to wether to grey out a table row is applicable (i'm using Core Data)
I've seen that you figured this one out already. However a cleaner and more future proof way would be to use a delegate protocol:
protocol DetailViewControllerDelegate: class {
func reloadTableView()
}
Then add a delegate property to your DetailViewController class and implement the call to the delegate:
class DetailViewController: UIViewController {
weak var delegate: DetailViewControllerDelegate?
....
func reloadMasterTableView() {
delegate?.reloadTableView()
}
}
And then in your MainViewController implement the delegate method:
extension MainViewController: DetailViewControllerDelegate {
func reloadTableView() {
tableView.reloadData()
}
}
Don't forget to set the delegate on your DetailViewController instances when you create them:
let detailViewController = DetailViewController()
detailViewController.delegate = self
I would suggest you use NSNotificationCenter .
If you want to to do it via Navigation controller here is to code should work for you in swift.
let navController: UINavigationController = self.splitViewController!.viewControllers[0] as! UINavigationController
let controller: MasterViewController = navController.topViewController as! MasterViewController
controller.tableView.reloadData()
Since I was able to access my viewController, I was able to access the parent viewcontroller like so:
func reloadMasterTableView(){
let navVC: UINavigationController = self.splitViewController!.viewControllers[0] as! UINavigationController
let sectionsVC : UIMasterViewController = navVC.topViewController as! UIMasterViewController
sectionsVC.tableView.reloadData()
}
I'm trying to wrap my head around protocols and delegates, but seems to be having some issues. I have 2 ViewControllers that I'm trying to pass data from. ViewController A has a text field that I want to be optionally populated from ViewController B. So there is a button on ViewController A that segues you over to ViewController B This is how I have B set up.
protocol AddPlayersViewControllerDelegate{
var playersName:String? { set get }
}
class B-Controller: UIViewController, UITableViewDelegate, UITableViewDataSource{
var addPlayerDelegate:AddPlayersViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
..etc
}
I'm using this code in my viewControllers B class to dismiss the currentView when a cell is selected
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("did select")
let cell = playerTableView.cellForRowAtIndexPath(indexPath)
addPlayerDelegate?.playersName? = "New Name"
if let navController = self.navigationController {
navController.popViewControllerAnimated(true)
}
}
It's not allowing me to set the players Name property inside the protocol here. It keeps returning me Nil when I check it from ViewController A.
View Controller A looks like this:
class A-ViewController: UIViewController, UITextFieldDelegate, AddPlayersViewControllerDelegate{
var addPlayerDelegate:AddPlayersViewControllerDelegate?
}
//then I'm just trying to print out the new name the was set in ViewController B
override func viewWillAppear(animated: Bool) {
println("this is the selected players name \(addPlayerDelegate?.playersName)") - returns nil
}
I'm sure I'm not fully understanding something, but I feel that I just keep reading and trying out examples only to end back up here where I started from.
//************************* UPDATE *************************//
I'm going to try and simplify my set up. I have 2 View Controllers, VC-A, and VC-B.
VC-A has a text field and a button. VC-B has a tableview. I want the option to have the textField to be populated from the cell.text from CB-B, but only if the user taps the button to view VC-B. So the first time that VC-A loads, it should being back nil from my playersName string from the protocol, because VC-B has never been called as of yet. But once the user taps the button inside VC-A to view VB-B and then selected a cell, which would dismiss VC-B and populate the playersName string inside the protocol on the VC-B class, then I'm using the viewWillAppear method to check to see if playersName has been set and if so use it. Here is my updated code from the help you have given me.
VC-A
class FirstViewController: AddPlayersViewControllerDelegate{
var playersName:String?
let svc = LookUpViewController()
override func viewDidLoad() {
super.viewDidLoad()
svc.addPlayerDelegate = self
}
}
VC-B
protocol AddPlayersViewControllerDelegate{
var playersName:String? { set get }
}
class LookUpViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var addPlayerDelegate: AddPlayersViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = playerTableView.cellForRowAtIndexPath(indexPath)
addPlayerDelegate?.playersName = "Ziggy"
println("the name to be pass is \(addPlayerDelegate?.playersName)")
if let navController = self.navigationController {
navController.popViewControllerAnimated(true)
}
}
It seems that I'm still getting nil even when I got back to VC-A from VC-B. All I want is to be able to get some data (string) from VC-B and use it in VC-A, but only after the user uses the VC-B class. Does that make sense?
You have shown that in the BController you have a property addPlayerDelegate:
var addPlayerDelegate:AddPlayersViewControllerDelegate?
And you have shown that in the BController you talk to that property:
addPlayerDelegate?.playersName? = "New Name"
But you have not shown that at any point in the lifetime of this controller, its addPlayerDelegate property is ever set to anything. For example I would want to see code like this:
someBController.addPlayerDelegate = someAController
If that doesn't happen, then that property remains at its initial value of nil.
Another problem with your code is that this line makes no sense:
class A-ViewController : // {
var addPlayerDelegate:AddPlayersViewControllerDelegate?
}
The AController doesn't need this property. They don't both need delegates! What the AController needs is a playersName property. Without it, it doesn't conform to the AddPlayersViewControllerDelegate protocol. In fact, I'm surprised that without that property your code even compiles. Are you sure you are reporting it correctly?