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?
Related
I have a ViewController with a variable which value is changing every second ( from a sensor ).
I made another ViewController let's call it SensorViewController with a Label on the screen in which I want to display the value from the main ViewController.
If I use override func prepare(for segue: UIStoryboardSegue, sender: Any?) the value is send but only one time ( it doesn't refresh/update every second ).
What can I do to change the value from SensorViewController every time the value from ViewController is changing?
Example:
// ViewController example code:
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
distanta1 = String(byteArray[0]) // variable which is changing every second
#IBAction func distantaSenzori(_ sender: UIButton) { //button which send me to SensorViewController
self.performSegue(withIdentifier: "goToSenzori", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // this one is sending value only when I press the button from above ( I have to exit from SensorViewController and enter again to see updated value )
if segue.identifier == "goToSenzori"{
let destinatieVC = segue.destination as! SensorViewViewController
destinatieVC.distance1 = distanta1 } }
}
// SensorViewController code:
class SensorViewViewController: UIViewController {
#IBOutlet weak var distanta1: UILabel!
var distance1: String?
override func viewDidLoad() {
super.viewDidLoad()
distanta1.text = distance2 }
}
Thank you very much, guys! You are awesome!
For Frankenstein:
In class SensorViewViewController my code looks like that:
var distance1: String?
override func viewDidLoad() {
super.viewDidLoad()
distanta1.text = distance1
print("Distance 1 is \(distance1)")
// Do any additional setup after loading the view.
}
It's called only once and the value is nil. What should I modify at the code here so the value to be refreshed?
I think a cleaner solution is to create a shared manager for handling the sensor. After that, you can notify your objects about the changing value. Of course in your case your "sensor" is something bluetooth but what I wrote is only a template basically, you can fill in your necessary methods and objects, delegates, so on.
class SensorManager {
static let shared: SensorManager = SensorManager()
private var sensor: Sensor
private init() {
sensor = Sensor()
}
//MARK: - Public methods
func startTheSensor() {
//This is what you call to start your sensor
}
func getSensorData() -> YourData {
//This is from where your objects can read the sensor data
}
//MARK: - Private methods -
private func didSensorUpdatedValue() {
//This is called whenever your sensor updates
.
.
.
let newSensorValue = "yourValue"
NotificationCenter.default.post(name: .init("SensorDataChanged"), object: nil)
}
}
In your viewcontroller:
deinit() {
NotificationCenter.default.removeObserver(self)
}
override func viewDidLoad() {
super.viewDidLoad()
weak var this = self
NotificationCenter.default.addObserver(this, selector: #selector(didSensorValueChanged), name: .init("SensorDataChanged"), object: nil)
}
#objc func didSensorValueChanged() {
SensorManager.shared.getSensorData()
}
You need to add an observer and keep reference to the destination view controller to keep passing in the new value that has been changed to the destination view controller. Here's how:
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
var distanta1 = String(byteArray[0]) {
didSet {
destinatieVC?.distance1 = distanta1
}
}
//...
var destinatieVC: SensorViewViewController?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToSenzori" {
destinatieVC = segue.destination as? SensorViewViewController
}
}
}
In SensorViewViewController:
var distance1: String? {
didSet {
print(distance1 ?? "")
}
}
Better approach: Set the destinatieVC?.distanta1.text = distanta1 directly if you're not doing anything else in the didSet block avoid the distance1 property entirely.
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 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.
I have a container view with multiple text boxes on it. I also have a button in Parent View controller(custom keypad). What I'm trying to do is select text box first & when I tap on the button I wanted some value to be populated to that last selected/focused textbox.
How can I do that? any alternative ways are welcome too. (I am having multiple container-views in the original code and try to use one keypad for all the views)
class MainViewController: UIViewController {
var weightVC : WeightViewController!
var focusedElement : UITextField
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "weight") {
weightVC = segue.destination as? WeightViewController
}
}
#IBAction func button1Clicked(_ sender: Any) {
if weightVC != nil {
weightVC.sampleTextBox1.text = "1"
//I want this sampleTextBox1 to be dynamic like weightVC.focusedInput = "1"
}
}
}
extension MainViewController:ChildToParentProtocol {
func setFocusedElement(with value: UITextField){
focusedElement = value
}
}
Container View Controller
protocol ChildToParentProtocol: class {
func setFocusedElement(with value:UITextField)
}
class WeightViewController: UIViewController {
weak var delegate: ChildToParentProtocol? = nil
#IBOutlet weak var sampleTextBox1: UITextField!
#IBOutlet weak var sampleTextBox2: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
// sampleTextBox1 Editing Did Begin event
#IBAction func editBeginSampleText1(_ sender: Any) {
print("edit begin")
delegate?.setFocusedElement(with: sampleTextBox1)
}
}
In other words, I simply want to keep a reference to last focused UITextFild when a button is tapped. Hope my requirement is clear enough. Please guide me if there is a way to achieve this.
Thanks
If I understood your question correctly you can keep track on which UITextField is tapped by using it's tag. And you can use UITextFieldDelegate to get the selected UITextField tag.
Consider the below code for WeightViewController
protocol ChildToParentProtocol: class {
//Changed value to Int for passing the tag.
func setFocusedElement(with value: Int)
}
import UIKit
class WeightViewController: UIViewController {
#IBOutlet weak var tf1: UITextField!
#IBOutlet weak var tf2: UITextField!
var selectedTFTag = 0
weak var delegate: ChildToParentProtocol? = nil
override func viewDidLoad() {
super.viewDidLoad()
//Assign delegate and tags to your TF
tf1.delegate = self
tf2.delegate = self
tf1.tag = 1
tf2.tag = 2
}
}
extension WeightViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
//Get the selected TF tag
selectedTFTag = textField.tag
//Pass tag to parent view
delegate?.setFocusedElement(with: selectedTFTag)
}
}
Now in your parent view ViewController you need to make some modification. I have added comments where I made changes to achieve your requirement.
import UIKit
//You need to confirm your ChildToParentProtocol with your UIViewController
class ViewController: UIViewController, ChildToParentProtocol {
var selectedTFTag = 0
var weightVC : WeightViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "weight" {
weightVC = segue.destination as? WeightViewController
//You need to pass delegate to containerview to make it working.
weightVC.delegate = self
}
}
#IBAction func btn1Tapped(_ sender: Any) {
//According to selected Tag perform your action
if selectedTFTag > 0 {
switch selectedTFTag {
case 1:
//set up first UITextField
weightVC.tf1.text = "First textfield was selected"
print("1")
case 2:
//set up second UITextField
weightVC.tf2.text = "Second textfield was selected"
default:
break
}
}
}
#IBAction func btn2Tapped(_ sender: Any) {
//According to selected Tag perform your action
if selectedTFTag > 0 {
switch selectedTFTag {
case 1:
//set up first UITextField
weightVC.tf1.text = "First textfield was selected"
print("1")
case 2:
//set up second UITextField
weightVC.tf2.text = "Second textfield was selected"
default:
break
}
}
}
func setFocusedElement(with value: Int) {
//Get selected TF tag with delegate
selectedTFTag = value
}
}
You can check THIS demo project for more info.
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])
}
}