I know the question is already here in the forum but I don't know why my delegate doesn't work. I work with them for the first time, by the way.
Here is the code
ViewController:
class ViewController: UIViewController, ContainerViewControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
let controller = ContainerViewController()
controller.containerDelegate = self
}
func didScrollChangeAppearanceBarButtonItem(change: Bool) {
if(change == true){
print("true")
}else{
print("false")
}
}
}
ContainerView:
protocol ContainerViewControllerDelegate {
func didScrollChangeAppearanceBarButtonItem(change: Bool)
}
class ContainerViewController: UIViewController, UIScrollViewDelegate{
var containerDelegate: ContainerViewControllerDelegate?
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if(velocity.y>0) {
containerDelegate?.didScrollChangeAppearanceBarButtonItem(change: false)
print("1")
} else {
containerDelegate?.didScrollChangeAppearanceBarButtonItem(change: true)
print("2")
}
}
}
What I am trying to do: When I scroll I want to send a bool to my ViewController. When the bool == true I want to something and when the bool == false I want to do something else.
I hope somebody can help me :)
instead of this:
let controller = ContainerViewController()
controller.containerDelegate = self
create the variable outside viewDidLoad():
var controller:ContainerViewController!
override func viewDidLoad() {
super.viewDidLoad()
controller = ContainerViewController()
controller.containerDelegate = self
}
The reason is that the controller being initialized inside the viewDidLoad gets deallocated once the viewDidLoad function reaches its end
Edit
I will elaborate, if you are trying to access your other view controller by segue, this is a wrong way. Instead do this:
self.performSegueWithIdentifier("your identifier", sender: self)
then add a function:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "Your identifier") {
guard let controller = segue.destination as? ContainerViewController else { return }
controller.delegate = self
}
}
You are allocating a ContainerViewController, which is deallocated about a microsecond later when the code leaves viewDidLoad, because there are no references to it.
Related
I have two view controllers (ViewController and ActionViewController) and one manager (Brain), the second view controller is shown when a user tapped on a button by a show segue created in storyboard and to get back to the first I use a self.dismiss in the second view controller.
The user enter a number on ActionViewController that need to be retrieved in ViewController. So I created Brain to use the delegate pattern.
The problem is that the delegate function inside ViewController is never run, I read other SO answers but nothing work. I used print statement to know where the code is not running anymore and the only block not running is the didUpdatePrice inside ViewController
Here is the code
ViewController
class ViewController: UIViewController, BrainDelegate {
var brain = Brain()
#IBOutlet var scoreLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
brain.delegate = self
scoreLabel.layer.cornerRadius = 25
scoreLabel.layer.masksToBounds = true
}
func didUpdateScore(newScore: String) {
print("the new label is \(newScore)")
scoreLabel.text = newScore
}
}
ActionViewController
class ActionViewController: UIViewController {
var brain = Brain()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addButtonTapped(_ sender: Any) {
brain.newAction(actualScore: 0, newActionValue: 5, isPositive: true)
self.dismiss(animated: true)
}
}
Brain
protocol BrainDelegate {
func didUpdateScore(newScore: String)
}
struct Brain {
var delegate: BrainDelegate?
func newAction(actualScore: Int, newActionValue: Int, isPositive: Bool) {
let newScore: Int
if isPositive {
newScore = actualScore + newActionValue
} else {
newScore = actualScore - newActionValue
}
print("the new score is \(newScore)")
delegate?.didUpdateScore(newScore: String(newScore))
}
}
You dont need an additional Brain class/struct at all, You can achieve it with simple protocol and default extension of protocol.
Step 1: Select your show segue and provide an identifier to that in storyboard as shown below
Step 2: In your ViewController add prepare(for segue method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "testIdentifier" {
guard let destinationViewController = segue.destination as? ActionViewController else { return }
destinationViewController.delegate = self
}
}
Step 3: In your ActionViewController declare a weak property named delegate
class ActionViewController: UIViewController {
weak var delegate: BrainDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addButtonTapped(_ sender: Any) {
delegate?.newAction(actualScore: 0, newActionValue: 5, isPositive: true)
self.dismiss(animated: true)
}
}
Step 4: Add class clause to your BrainDelegate (Class bound protocol) so that you can hold a weak reference to delegate
protocol BrainDelegate: class {
func didUpdateScore(newScore: String)
func newAction(actualScore: Int, newActionValue: Int, isPositive: Bool)
}
Step 5:
Add a default extension to BrainDelegate and provide default implementation of newAction(actualScore:
extension BrainDelegate {
func newAction(actualScore: Int, newActionValue: Int, isPositive: Bool) {
let newScore: Int
if isPositive {
newScore = actualScore + newActionValue
} else {
newScore = actualScore - newActionValue
}
print("the new score is \(newScore)")
self.didUpdateScore(newScore: String(newScore))
}
}
Step 6: In your ActionViewController simply trigger delegate methods as
#IBAction func addButtonTapped(_ sender: Any) {
delegate?.newAction(actualScore: 0, newActionValue: 5, isPositive: true)
self.dismiss(animated: true)
}
This should do the job
First, on Brain you should use class, not struct. That is because when you use struct, passing the variable to another will make a copy, it will not use the same reference. And class will only copy the reference.
That means that your Brain struct will lose the delegate assigned on .delegate = self
second, you need to use the same instance on the second viewController and the first. like this:
on the first viewController
var brain = Brain()
// this one is the one that you will put your "brain.delegate = self"
on the second viewController, you will need to inject this variable from the first viewController into the second. That is to keep the same instance on both. And this will make the delegate callable.
to do this with storyboard you will do on the first ViewController:
// this function should be called when the next viewController should open.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destination) {
case let vc as MyViewController:
vc.brain = self.brain
default:
break
}
}
super.prepare(for: segue, sender: sender)
}
inside the second viewController, use:
var brain: Brain?
So what I'm trying to do is pass a String and an Int back from one ViewController (NewCellViewController) to the previous one (SecondScreenViewController) when I close it. I added a print statement in the method in SecondScreenViewController that is supposed to receive this data, and it didn't print so I guess the method never ran. This is my code (removed some stuff to only include whats relevant):
SecondScreenViewController:
import UIKit
protocol DataDelegate {
func insertEvent(eventString: String, pos: Int)
}
class SecondScreenViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, DataDelegate {
var eventNames = ["event1", "event2", "event3"]
override func viewDidLoad() {
super.viewDidLoad()
advance()
}
//DataDelegate methods
func insertEvent(eventString: String, pos: Int)
{
print("if this prints, it worked")
if pos == -1
{
eventNames.append(eventString)
}
else
{
eventNames.insert(eventString, at: pos)
}
}
#objc func advance()
{
let vc = NewCellViewController(nibName: "NewCellViewController", bundle: nil)
vc.delegate = self
present(vc, animated: true, completion: nil)
}
}
NewCellViewController:
import UIKit
class NewCellViewController: UIViewController {
var delegate:DataDelegate?
#IBOutlet var addEventName: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addItem() {
insertNewEvent()
dismiss(animated: true, completion: nil)
}
func insertNewEvent()
{
let eventName = addEventName!.text
delegate?.insertEvent(eventString: eventName!, pos: -1) //add different positions
}
}
I have used the same controller name as yours, just to make you understand better.
SecondScreenViewController
import UIKit
class SecondScreenViewController: UIViewController {
var eventNames = ["event1", "event2", "event3"]
override func viewDidLoad() {
super.viewDidLoad()
//advance()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
advance()
}
private func advance() {
// Dont forget to add `Storyboard ID` as "NewCellViewController" on your Main.storyboard.
// See the image attached below.
if let vc = self.storyboard?.instantiateViewController(identifier: "NewCellViewController") as? NewCellViewController {
vc.delegate = self
present(vc, animated: true)
}
}
}
// Better this way.
extension SecondScreenViewController: DataDelegate {
func insertEvent(eventString: String, pos: Int) {
print(eventString, pos)
}
}
NewCellViewController
import UIKit
// Better to create protocols here.
protocol DataDelegate: class {
func insertEvent(eventString: String, pos: Int)
}
class NewCellViewController: UIViewController {
weak var delegate: DataDelegate?
#IBOutlet var addEventName: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addItem() {
insertNewEvent()
dismiss(animated: true)
}
private func insertNewEvent() {
delegate?.insertEvent(eventString: "your text", pos: -1)
}
}
Hope, this helps.
You can try using segue in the first VC to push the Second VC and pop the Second VC and come back to First VC. This might help you work with the delegate.
And also you can use the UserDefaults to pass and synchronize such values.
Depending on what exactly you want to pass you could use user defaults. Not recommended by many but I feel for simple data it's really quick and effective
I want to trigger Navigation controller to some other screen when i press the button in UIView class. How can i do this?
//Code for UIView Class in Which Button Iboutlet is created
import UIKit
protocol ButtonDelegate: class {
func buttonTapped()
}
class SlidesVC: UIView {
var delegate: ButtonDelegate?
#IBAction func onClickFinish(_ sender: UIButton) {
delegate?.buttonTapped()
}
#IBOutlet weak var imgProfile: UIImageView!
}
//ViewController Class code in Which Button Protocol will be entertained
class SwipingMenuVC: BaseVC, UIScrollViewDelegate {
var slidesVC = SlidesVC()
override func viewDidLoad() {
super.viewDidLoad()
slidesVC = SlidesVC()
// add as subview, setup constraints etc
slidesVC.delegate = self
}
extension BaseVC: ButtonDelegate {
func buttonTapped() {
self.navigationController?.pushViewController(SettingsVC.settingsVC(),
animated: true)
}
}
A more easy way is to use typealias. You have to write code in 2 places. 1. your viewClass and 2. in your View Controller.
in your SlidesView class add a typealias and define param type if you need otherwise leave it empty.
class SlidesView: UIView {
typealias OnTapInviteContact = () -> Void
var onTapinviteContact: OnTapInviteContact?
#IBAction func buttonWasTapped(_ sender: UIButton) {
if self.onTapinviteContact != nil {
self.onTapinviteContact()
}
}
}
class SwipingMenuVC: BaseVC, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let slidesView = SlidesView()
slidesView.onTapinviteContact = { () in
// do whatever you want to do on button tap
}
}
You can use the delegate pattern to tell the containing ViewController that the button was pressed and let it handle whatever is needed to do next, The view doesn't really need to know what happens.
A basic example:
protocol ButtonDelegate: class {
func buttonTapped()
}
class SomeView: UIView {
var delegate: ButtonDelegate?
#IBAction func buttonWasTapped(_ sender: UIButton) {
delegate?.buttonTapped()
}
}
class ViewController: UIViewController {
var someView: SomeView
override func viewDidLoad() {
someView = SomeView()
// add as subview, setup constraints etc
someView.delegate = self
}
}
extension ViewController: ButtonDelegate {
func buttonTapped() {
self.showSomeOtherViewController()
// or
let vc = NewViewController()
present(vc, animated: true)
}
}
So I have the following layout for my iOS app.
What I'm intending to do is put a table view in the purpleVC to control the Green viewcontroller...the top peachVC will have text in it which will need to change. I'm just not sure how to control one view controller from another. This includes having the purple slide in and out when a button on the GreenVC is clicked. I know there are classes out there to do this however I want to learn as well.
TESTING DELEGATES:
MAINVIEW CONTROLER
import UIKit
protocol Purpleprotocol {
func buttonpressed()
}
protocol Greenprotocol {
}
extension UIViewController {
func alert(message: String, title: String = "") {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
}
class MainViewController: UIViewController,Purpleprotocol,Greenprotocol {
weak var infoNav : UINavigationController?
weak var greenVC: GreenVC?
weak var purpleVC: PurpleVC?
weak var peachVC: PeachVC?
func buttonpressed() {
alert(message: "This is message")
print("buttonpressed")
let date = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minutes = calendar.component(.minute, from: date)
greenVC?.greenlabel.text = String(hour) + ":" + String(minutes)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "contentSegue" {
let infoNav = segue.destination as! UINavigationController
}
}
}
PURPLEVIEW CONTROLER
class PurpleVC: UIViewController {
var delegate: Purpleprotocol?
#IBAction func butclick(_ sender: UIButton) {
alert(message: "infunction")
delegate?.buttonpressed()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
Thanks
R
It does depend on the case but to see a few examples:
A) You can connect it through the delegates. Your main view controller has 3 child view controllers to which it should report changes. It should also assign itself as a delegate to all 3 child controllers where it will get all notifications for events. This would look like
func purpleViewController(sender: PVC, selectedItem: Item) {
self.greenViewController.showItem(item: selectedItem)
self.peachVC.showItem(item: selectedItem)
}
func purpleViewController(sender: PVC, shouldSetMenuClosed closed: Bool) {
self.menuConstraint.constant = closed ? targetWidth : 0.0
}
B) You may have a data model which controls the whole screen and has a delegate for each of the children. The model will report any changes to its delegates so they may react accordingly. Main view controller would create an instance of this model when it loads and pass it to all of the children view controllers. The children would then directly manipulate the model:
In green controller:
func onTap() {
mode.menuShown = !mode.menuShown
}
In model:
var menuShown: Bool = true {
didSet {
self.menuDelegate.model(self, changedMenuShownStateTo: menuShown)
}
}
In main view controller:
func model(_ sender: Model, changedMenuShownStateTo shown:Bool) {
self.menuConstraint.constant = shown ? 0.0 : targetWidth
}
C) You can use notifications where any of the controllers may post to notification center a custom notification and other controllers may observe the notifications and act accordingly.
There are many other ways in doing so but these probably most popular. See if any of them fits you...
Delegation.
Your MainViewController will become a delegate to each of the embedded VC's that want to pass back information. From your description you'll need two delegate relationships:
protocol PurpleProtocol {
func selected(row: Int, text: String)
}
protocol GreenProtocol {
func slideButtonPressed()
}
Have MainViewController implement these protocols. Give identifiers to the embed segues. You can find them in the Document Outline view. In prepareForSegue, retain pointers to the embedded VC's and pass your self as the delegate:
class MainViewController: UIViewController, PurpleProtocol, GreenProtocol {
weak var greenVC: GreenViewController?
weak var purpleVC: PurpleViewController?
weak var peachVC: PeachViewController?
func selectedRow(row: Int, text: String) {
// do something useful
}
func slideButtonPressed() {
// slide purple view in or out depending on current state
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EmbedPurple" {
let dvc = segue.destination as! PurpleViewController
purpleVC = dvc
dvc.delegate = self
}
else if segue.identifier = "EmbedGreen" {
let nav = segue.destination as! UINavigationController
let dvc = nav.topViewController as! GreenViewController
greenVC = dvc
dvc.delegate = self
} else if segue.identifier = "EmbedPeach" {
peachVC = segue.destination as! PeachViewController
}
}
}
In your embedded VC's, add a delegate pointer and call the delegate with the protocol method when it is time:
class GreenViewController: UIViewController {
weak var delegate: GreenProtocol?
#IBAction slideButtonPressed(sender: UIButton) {
delegate?.slideButtonPressed()
}
}
class PurpleViewController: UITableViewController {
weak var delegate: PurpleProtocol?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.selected(row: indexPath.row, text: modelArray[indexPath.row])
}
}
Effect I Wish to Replicate
Example 1) https://youtu.be/EgU7BfOJ3BE
Example 2) https://youtu.be/kfBzZeGRSa0
In both examples, the user taps on a bar button item located in the navigation bar, and the List view controller segues to the Map view controller using the Flip Horizontal transition.
I have tried using container views, but cannot seem to get this working (I always get a full screen transition, rather than only the container view transitioning).
Any help is greatly appreciated!!!
Edit
Here is my attempt so far (link to the full Xcode project).
ParentVC (one with navigation bar):
import UIKit
protocol ButtonPressedDelegate
{
func buttonPressed(passedData:Bool)
}
class ParentViewController: UIViewController
{
var delegate:ButtonPressedDelegate? = nil
var switchValue:Bool = true
var childViewController:ChildViewController?
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
#IBAction func switchButtonPressed(_ sender: Any)
{
if switchValue
{
switchValue = false
}
else
{
switchValue = true
}
childViewController?.buttonPressed(passedData: switchValue)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "parentToChild"
{
childViewController = segue.destination as? ChildViewController
childViewController?.buttonPressed(passedData: switchValue)
}
}
}
ChildVC:
import UIKit
class ChildViewController: UIViewController, ButtonPressedDelegate
{
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
func buttonPressed(passedData: Bool)
{
if passedData
{
print("Show Blue")
performSegue(withIdentifier: "showBlue", sender: self)
}
else
{
print("Show Red")
performSegue(withIdentifier: "showRed", sender: self)
}
}
}