How to enable scrolling from another controller? - ios

I have 2 controllers: ViewController and GalleryViewController(with the collection view on it). From the storyboard I set for the collectionView in GalleryViewController Scrolling Enabled to false. Now, how can I change it to true from the ViewController?
I've tried this:
var vc: GalleryViewController?
vc.collectionView.scrollEnabled = true
but it does not work. Is there another solution with which I can change scrolling from another controller(ViewController)?

your code will create a new instance of GalleryViewController and you need to use the existing one.
You have a number of options, partly depending on how you navigate from ViewController to GalleryViewController.
If you are creating the Gallery View from your initial controller, then you should use prepareForSegue, something like this
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
if segue!.identifier == "GallerySegueOrWhateverYouHaveCalledIt" {
let viewGalleryController:ViewGalleryController = segue!.destinationViewController as ViewGalleryController
let collectionViewLink = viewGalleryController.collectionView
}
}
If you're using a Tab Controller, and assuming you know the index of your GalleryView, let's call it indexGalleryView, then it's even easier
var vc = tabBarController!.viewControllers![indexGalleryView] as! GalleryViewController
vc.collectionView.scrollEnabled = true
And if you have a ViewController -> Container -> Embed GalleryViewController -> CollectionView, you can get a handle to the embedded ViewController in the viewDidLoad of the top level controller like this
for vc in self.childViewControllers
{
if vc.isKindOfClass(GalleryViewController)
{
myGalleryViewController = vc as! GalleryViewController
}
}
once you have myGalleryViewController you should be able to access everything on the child view

Related

Find out from where ViewController was opened

I have a TableViewController and 2 ways to get there.
one is a segue(show) from a Viewcontroller that is the root controller of a Navigation Controller, which itself is a tab of my Tab Bar Controller.
second, the tableVC is also a root VC of antoher Navigation Controller, that is also a tab in that Tab Bar Controller. Here is an illustration:
Now i want to check in the viewDidLoad if my TableVC, whether it is called by the first or by the second way. How can i find that out?
You could add a property to your view controller that indicates where it came from…
class MyTableViewController: UITableViewController {
enum Source {
case productList, basket
}
var source: Source!
}
then
override func prepareForSegue(segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? MyTableViewController {
vc.source = .productList
}
}
etc
very simple just add a var on your viewController which you are going to display , lets assume var vcOpenedBy = ""
now when launching this ViewController , just use this var and print whatever you feel comfortable like this
let vc = VcController(nibName:"",bundle:nil)
vc.vcOpenedBy = "NavigationMethod"
and at your segue you can also use this vcOpenedBy
and use string "SqgueMethod"
now on that viewController in viewDidLoad()
just print this vc . thats it

Pass data to View Controller embedded inside a Container View Controller

My view controller hierarchy is the following:
The entry point is a UINavigationController, whose root view controller is a usual UITableViewController. The Table View presents a list of letters.
When the user taps on a cell, a push segue is triggered, and the view transitions to ContainerViewController. It contains an embedded ContentViewController, whose role is to present the selected letter on screen.
The Content View Controller stores the letter to be shown as a property letter: String, which should be set before its view is pushed on screen.
class ContentViewController: UIViewController {
var letter = "-"
#IBOutlet private weak var label: UILabel!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
label.text = letter
}
}
On the contrary, the Container View Controller should not know anything about the letter (content-unaware), since I'm trying to build it as reusable as possible.
class ContainerViewController: UIViewController {
var contentViewController: ContentViewController? {
return childViewControllers.first as? ContentViewController
}
}
I tried to write prepareForSegue() in my Table View Controller accordingly :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let containerViewController = segue.destinationViewController as? ContainerViewController {
let indexPath = tableView.indexPathForCell(sender as! UITableViewCell)!
let letter = letterForIndexPath(indexPath)
containerViewController.navigationItem.title = "Introducing \(letter)"
// Not executed:
containerViewController.contentViewController?.letter = letter
}
}
but contentViewController is not yet created by the time this method is called, and the letter property is never set.
It is worth mentioning that this does work when the segue's destination view controller is set directly on the Content View Controller -- after updating prepareForSegue() accordingly.
Do you have any idea how to achieve this?
Actually I feel like the correct solution is to rely on programmatic instantiation of the content view, and this is what I chose after careful and thorough thoughts.
Here are the steps that I followed:
The Table View Controller has a push segue set to ContainerViewController in the storyboard. It still gets performed when the user taps on a cell.
I removed the embed segue from the Container View to the ContentViewController in the storyboard, and I added an IB Outlet to that Container View in my class.
I set a storyboard ID to the Content View Controller, say… ContentViewController, so that we can instantiate it programmatically in due time.
I implemented a custom Container View Controller, as described in Apple's View Controller Programming Guide. Now my ContainerViewController.swift looks like (most of the code install and removes the layout constraints):
class ContainerViewController: UIViewController {
var contentViewController: UIViewController? {
willSet {
setContentViewController(newValue)
}
}
#IBOutlet private weak var containerView: UIView!
private var constraints = [NSLayoutConstraint]()
override func viewDidLoad() {
super.viewDidLoad()
setContentViewController(contentViewController)
}
private func setContentViewController(newContentViewController: UIViewController?) {
guard isViewLoaded() else { return }
if let previousContentViewController = contentViewController {
previousContentViewController.willMoveToParentViewController(nil)
containerView.removeConstraints(constraints)
previousContentViewController.view.removeFromSuperview()
previousContentViewController.removeFromParentViewController()
}
if let newContentViewController = newContentViewController {
let newView = newContentViewController.view
addChildViewController(newContentViewController)
containerView.addSubview(newView)
newView.frame = containerView.bounds
constraints.append(newView.leadingAnchor.constraintEqualToAnchor(containerView.leadingAnchor))
constraints.append(newView.topAnchor.constraintEqualToAnchor(containerView.topAnchor))
constraints.append(newView.trailingAnchor.constraintEqualToAnchor(containerView.trailingAnchor))
constraints.append(newView.bottomAnchor.constraintEqualToAnchor(containerView.bottomAnchor))
constraints.forEach { $0.active = true }
newContentViewController.didMoveToParentViewController(self)
}
} }
In my LetterTableViewController class, I instantiate and setup my Content View Controller, which is added to the Container's child view controllers. Here is the code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let containerViewController = segue.destinationViewController as? ContainerViewController {
let indexPath = tableView.indexPathForCell(sender as! UITableViewCell)!
let letter = letterForIndexPath(indexPath)
containerViewController.navigationItem.title = "Introducing \(letter)"
if let viewController = storyboard?.instantiateViewControllerWithIdentifier("ContentViewController"),
let contentViewController = viewController as? ContentViewController {
contentViewController.letter = letter
containerViewController.contentViewController = contentViewController
}
}
}
This works perfectly, with an entirely content-agnostic container view controller. By the way, it used to be the way one instantiated a UITabBarController or a UINavigationController along with its children, in the appDidFinishLaunching:withOptions: delegate method.
The only downside of this I can see: the UI flow ne longer appears explicitly on the storyboard.
The only way I can think of is to add delegation so that your tableViewController implements a protocol with one method to return the letter; then you have containerViewController setting its childViewController (the contentViewController) delegate to its parent. And the contentViewController can finally ask its delegate for the letter.
At your current solution the presenting object itself is responsible for working both with the "container" and the "content", it doesn't have to be changed, but such solution not only has the issues like the one you described, but also makes the purpose of the "container" not very clear.
Look at the UIAlertController: you are not configuring its child view controller directly, you are not even supposed to know it exists when using the alert controller. Instead of configuring the "content", you are configuring the "container" which is aware of the content interfaces, lifecycle and behavior and doesn't expose it. Following this approach you achieve a properly divided responsibility of the container and content, minimal exposure of the "content" allows you to update the "container" without a need to update the way it is used.
In short, instead of trying to configure everything from a single place, make it so you configure only the "container" and let it configure the "content" when and where it is needed. E.g. in the scenario you described the "container" would set data for the "content" whenever it initializes the child controllers. I'm using "container" and "content" instead of ContainerViewController and ContentViewController because the solution is not strictly based on the controllers because you might as well replace it wth NSObject + UIView or UIWindow.

How to send values to a parent view controller in Swift

I am working on a settings view controller screen for my iOS app written in swift. I am using a navigation controller to run the main settings table view which shows the cell titled, "Input Method." The current method is listed on the right of the cell. They can click the cell to go to the next view controller where they can select the input method that they'd like.
From here, there are two sections. The first is the input method to choose (touchscreen or joystick). The second section is joystick specific on whether or not the person is a lefty or righty. I don't want to have the vc unwind when they choose one box because they may choose one in another section too.
My question: How can I update the text field in the parent controller from the child controller.
Problems I'm having for optional solutions:
let parentVC: UIViewController = (self.navigationController?.parentViewController)!
parentVC.inputMethod.text? = cellSelected // This doesn't work because it cannot find the label inputMethod.
viewDidLoad() will cause a lag and the user sees the old method before it changes.
I cannot find out how to run a segue when someone clicks the back button at the upper left hand side in the navigation controller, since the navigation controller controls the segue.
It is not a good idea to cast the parent view controller, even when you are sure which class represents. I'll do it with a protocol:
In the child controller add a protocol like:
protocol ChildNameDelegate {
func dataChanged(str: String)
}
class ChildClass {
weak var delegate: ChildNameDelegate?
func whereTheChangesAreMade(data: String) {
delegate?.dataChanged(data)
}
}
And in the parent:
class ParentClass: ChildNameDelegate {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let segueId = segue.identifier else { return }
switch segueId {
case "childSegue":
let destVC = segue.destinationViewController as! ChildClass
destVC.delegate = self
break
default:
break
}
}
// Child Delegate
func dataChanged(str: String) {
// Do whatever you need with the data
}
}
You need to cast the parentViewController to whatever custom class it has. For example, if the parent has the class ExampleParentController, you would write:
let parentVC = (self.navigationController?.parentViewController)! as! ExampleParentController
parentVC.inputMethod.text? = cellSelected
I found a solution here: Modifing one variable from another view controller swift
http://www.raywenderlich.com/115300/swift-2-tutorial-part-3-tuples-protocols-delegates-and-table-views
Instead of trying to access the view controller directly (which would be easier if it weren't returning a nil for the view controller) you can use a delegate method to adjust the variables.
The delegate worked like a charm!

Trying to hide a view based on what button I click using prepareforsegue

In my FirstViewController I have two buttons called (button1 and button2).
In my SecondViewController I have two views called(visible1 and visible2).
When I push button1, I will switch to SecondViewController and both
Views should be visible.
When I push button2, only the View(visible2) should be visible.
I tried this:
if (segue.identifier == "segueTest2") {
let svc = segue.destinationViewController as! SecondViewController
svc.visible1.hidden = true
}
but then i will get an error:
When prepareForSegue is called, destinationViewController.view is not loaded yet (and outlets are not connected as well). So visible1 is nil at this point.
IMO best option would be to create some variable mode on second view controller that can take values from enum .AllVisible / .OnlySecondVisible. The in prepareForSegue you set svc.mode = .OnlySecondVisible. And in SecondViewController.viewDidLoad you configure your UI according to selected mode. So first view controller not editing second view controller UI directly. Weak coupling is good.
Another (easier) workaround is to write:
if (segue.identifier == "segueTest2") {
let svc = segue.destinationViewController as! SecondViewControlle
let _ = svc.view // trigger viewDidLoad
svc.visible1.hidden = true
}
But it's poor code design...

Swift how to create a protocol delegate from root VC to embedded VC

I have a root VC that embeds a table view through a container view with segue. So that makes both the root VC and child VC visible at the same time.
if segue.identifier == "TableSegue" {
let toView = segue.destinationViewController as! TableViewController
toView.delegate = self
}
How can I implement a protocol delegate between the root vc and child vc since the child VC is embedded inside the root vc?
What I want to do is to have a functions fired in the child VC once a button in root VC is clicked.
I have tried to implement a protocol delegate the normal way but it seems to not be picked up in the child VC
protocol TableViewInterface {
func someWork()
}
class RootVC:UIViewController {
var delegate: TableViewInterface?
func callDelegate() {
delegate?.someWork()
}
}
class TableViewController: UITableViewController, TableViewInterface {
func someWork() {
//Perform your the work you want done or the action you want fired in your tableView here...
}
}
The above is an example of a standard delegate pattern in swift. It looks like you are trying to set the delegate in prepareForSegue(), but IRRC it won't get called with a containerView. You probably want to get a reference to you tableView through your container view.
You should be able to get a reference to it by doing something like this in your RootVC
if let tableVC = childViewControllers[0] as? TableViewController {
self.tableViewController = tableVC
}
I hope this all makes sense

Resources