In my project I have UINavigationController with three embedded UIViewControllers. On the first one I add balanceLabel and refreshButton to navigation bar. When click on button first view controller send url request and show return value on label.
#IBAction func refreshButtonAction(_ sender: UIBarButtonItem) {
let operation = GetInfoOperation(...)
operation.completionBlock = { [weak self] in
DispatchQueue.main.async {
guard let balance = operation.output?.value?.balance else { return }
self?.balanceLabel.text = balance
let significantDigits = Int(Double(balance.toInt64!) * pow(10, -10))
}
}
queue.addOperation(operation)
}
How can I get the same behaviour on other ViewControllers without duplicate #IBAction func refreshButtonAction(_ sender: UIBarButtonItem) in each ViewController?
you can archive this by extension, using inheritance,
create the view controller you want with all the common feature you want and then instead of inheriting directly from UIViewController inherit from that base viewController
your base controller BaseViewController
class BaseViewController: UIViewController {
//all comman functionallity
//add your button here
}
class ChildOneViewController: BaseViewController {
//this class will get all the functionality from BaseViewController
//you can access the BaseViewController button here
//add function for ChildOneViewController
}
class ChildtwoViewController: BaseViewController {
//this class will get all the functionality from BaseViewController
//you can access the BaseViewController button here
//add function for ChildtwoViewController
}
Related
I need to pass a String and Array from my Third ViewController to my First ViewController directly using protocol/delegate, I have no problem doing it from VC 2 to VC 1 but I'm having a hard time with this. Also after clicking a button in my VC3 I need to go back to VC 1 and update the VC UI how would I do that? Would that have to be in viewdidload?
This in Swift UIKit and Storyboard
You need two protocols, and your firstVC and SecondVC have to conform those. When pushing new ViewController you need to give the delegate of that ViewController to self. On your third VC, when you click the button you need to call your delegate and pass your data to that delegate method, then repeat the same for other.
For FirstVC
protocol FirstProtocol: AnyObject {
func firstFunction(data: String)
}
class FirstVC: UIViewController, FirstProtocol {
weak var delegate: FirstProtocol?
#IBAction func buttonClicked(_ sender: Any) {
let secondVC = SecondVC()
secondVC.delegate = self
navigationController?.pushViewController(secondVC, animated: true)
}
func firstFunction(data: String) {
navigationController?.popToRootViewController(animated: true)
print(data)
}
}
You handle your navigation from your root. For better experience you can use something like coordinator pattern to handle it.
protocol SecondProtocol: AnyObject {
func secondFunction(data: String)
}
class SecondVC: UIViewController, SecondProtocol {
weak var delegate: FirstProtocol?
#objc func buttonClicked() {
let thirdVC = ThirdVC()
thirdVC.delegate = self
navigationController?.pushViewController(thirdVC, animated: true)
}
func secondFunction(data: String) {
delegate?.firstFunction(data: data)
}
}
Second VC is something that you just need to pass parameters.
class ThirdVC: UIViewController {
weak var delegate: SecondProtocol?
#objc func buttonClicked() {
delegate?.secondFunction(data: "data") // PASS YOUR ARRAY AND STRING HERE
}
}
What you need is unwind segue. Unwind segue will act like segue, only backward, popping, in this case, VC2. You can read here for more information.
Updating data code would be put in a function similar to prepareToSegue() but for unwind segue in your VC1.
Example of the function inside VC1:
#IBAction func unwindToDestination(_ unwindSegue: UIStoryboardSegue) {
switch unwindSegue.identifier {
case SegueIdentifier.yourSegueIdentifier:
let sourceVC = unwindSegue.source as! SourceVC
dataToPass = sourceVC.dataToPass
reloadData()
default:
break
}
}
Here is a different approach that accomplishes what you described by performing a Present Modally segue directly from View Controller 3 to View Controller 1, and sharing the string and array values by way of override func prepare(for segue....
In Main.storyboard, I set up 3 View Controllers, and have segues from 1 to 2, 2 to 3, and 3 to 1. These are Action Segues directly from the buttons on each VC, which is why you won't see self.performSegue used inside any of the View Controller files. Here is the picture:
In the first view controller, variables are initialized (with nil values) that will hold a String and an Array (of type Int in the example, but it could be anything):
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var updatableTextLabel: UILabel!
var string: String?
var array: [Int]?
override func viewDidLoad() {
super.viewDidLoad()
// These will only not be nil if we came here from the third view controller after pressing the "Update First VC" button there.
// The values of these variables are set within the third View Controller's .prepare(for segue ...) method.
// As the segue is performed directly from VC 3 to VC 1, the second view controller is not involved at all, and no unwinding of segues is necessary.
if string != nil {
updatableTextLabel.text = string
}
if let a = array {
updatableTextLabel.text? += "\n\n\(a)"
}
}
}
The second view controller doesn't do anything except separate the first and third view controllers, so I didn't include its code.
The third view controller assigns the new values of the string and array inside prepare (this won't be done unless you press the middle button first, to demonstrate both possible outcomes in VC 1). This is where your string and array get passed directly from 3 to 1 (skipping 2 entirely).
import UIKit
class ThirdViewController: UIViewController {
var theString = "abcdefg"
var theArray = [1, 2, 3]
var passValuesToFirstVC = false
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func updateFirstVC(_ sender: UIButton) {
passValuesToFirstVC = true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if passValuesToFirstVC && segue.identifier == "toFirst" {
// Cast segue.destination (which on its own has type UIViewController, the superclass to all your custom View Controllers) to the specific subclass that your first View Controller belongs to
let destinationVC = segue.destination as! FirstViewController
// When your first view controller loads, it will receive these values for the 'string' and 'array' variables. They replace the original 'nil' values these had in the FirstViewController definition.
destinationVC.string = theString
destinationVC.array = theArray
}
}
}
Note that there is an IBOutlet to the label on the first View Controller which contains the text to be updated.
After visiting the third view controller, pressing the "Update First VC Text" button, and then performing the segue back to the first, here is how it will look:
This doesn't address the part about protocols and delegates in your question (as I'm not sure how they're being used in your program, and other answers have already addressed that), but it illustrates the method of transferring variables directly from one View Controller to another without unwinding segues or using the UINavigationController.
I have some buttons in Child View Controller. I'd like a user to be able to click on these buttons, and, in case he clicks in other area, Parent View Controller to be able to process those clicks that Child View Controller doesn't work with.
You need to create protocols and delegates.
protocol Vc2Delegate : AnyObject {
func buttonPressed()
}
class Vc1 : UIViewController, Vc2Delegate {
func presentVC2() {
let vc2 = Vc2()
vc2.delegate = self
present(vc2)
}
func buttonPressed() {
// process vc2 button press in vc1
}
}
class Vc2 : UIViewConttoller {
// weak !!!!
weak var delegate : Vc2Delegate? = nil
func buttonInVc2Pressed() {
// call func in vc1 from vc2
delegate?.buttonPressed()
}
}
P.S. Sorry for formatting, was wrote from iPhone.
At the moment I have a ViewController class containing a UIScrollView, within the scroll view I have another view controller, where I can currently receive gesture recognition. My goal is to be able to perform a segue to a different view controller, based on which subViewController I tap.
let scrollView = UIScrollView(frame: CGRect(x:0,y:0, width: self.view.frame.width, height:self.view.frame.height-106))
scrollView.delegate = self;
self.view.addSubview(scrollView);
let subView11 = subView(nibName: nil, bundle: nil);
subView1.view.frame = CGRect(x:0,y:0, width: self.view.frame.width, height: CGFloat(openReelHeight));
self.addChildViewController(subView1);
scrollView.addSubview(subView1.view);
subView.didMove(toParentViewController: self);
Then in the subView class I have a basic touch recognition function:
#IBAction func tapOnView(_ sender: UITapGestureRecognizer) {
//change main View controller
}
I would suggest letting the parent perform the segue. So you need a mechanism to let the child inform the parent that the button has been tapped. Here are two approaches:
The child view controller can define a protocol and then have its #IBAction for the button invoke that in the parent view controller.
protocol ChildViewControllerDelegate {
func child(_ child: ChildViewController, didTapButton button: Any)
}
class ChildViewController: UIViewController {
#IBAction func didTapButton(_ sender: Any) {
if let parent = parent as? ChildViewControllerDelegate {
parent.child(self, didTapButton: sender)
}
}
}
Clearly, the parent view controller needs to conform to that protocol:
extension ViewController: ChildViewControllerDelegate {
func child(_ child: ChildViewController, didTapButton button: Any) {
// now segue to whatever you want
}
}
You can alternatively follow an explicit protocol-delegate pattern, rather than relying upon the view controller containment relationships of the parent:
protocol ChildViewControllerDelegate: class {
func didTapButton(_ sender: Any)
}
class ChildViewController: UIViewController {
weak var delegate: ChildViewControllerDelegate?
#IBAction func didTapButton(_ sender: Any) {
delegate?.didTapButton(sender)
}
}
And then, when the parent adds the child, it would have to explicitly set the delegate:
let child = storyboard!.instantiateViewController(withIdentifier: "ChildViewController") as! ChildViewController
addChildViewController(child)
child.delegate = self
// add the child's view to your view hierarchy however appropriate for your app
child.didMove(toParentViewController: self)
And, of course, the parent again has to conform to this protocol:
extension ViewController: ChildViewControllerDelegate {
func didTapButton(_ sender: Any) {
// segue to next scene
}
}
Note, with both of these approaches, you can change your protocol's func to include whatever parameters you want (e.g. passing back contents of some UITextField or whatever). Likewise, you might use method names that make the functional intent of the child a little more explicit. I used somewhat generic method and protocol names because I don't know what the various children are doing.
I have the following class below. The idea is it will use a custom Progress Window View Controller to handle progress of various different events. The problem is since this is in a class and not a view controller it's self, I'm not sure how to make the progressWindow actually show up after I instantiate it from the storyboard?
How do I do this? Currently I get an error that the application tried to present model view controller on itself.
import Foundation
import UIKit
class StatusProgress{
static var cancelCode = {}
static var runCode = {}
static var theProgressWindowController = ProgressWindowViewController()
static var returningViewControllerIdentifier = ""
static let storyboard = UIStoryboard(name: "Main", bundle: nil)
static func run(){
// This will run in parralel but on main queue. Has to be on this Queue because it might involve UI
dispatch_async(dispatch_get_main_queue(), {
// Update the UI on the main thread.
StatusProgress.runCode()
});
}
static func cancel(){
dispatch_async(dispatch_get_main_queue(), {
StatusProgress.cancelCode()
dispatch_sync(dispatch_get_main_queue(),{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier(returningViewControllerIdentifier)
vc.presentViewController(vc, animated: true, completion: nil)
})
});
}
static func show(){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
theProgressWindowController.presentViewController(theProgressWindowController, animated: true, completion: nil) //use own instance to show it's self? (throws error! application tried to present modal view controller on itself. Presenting controller is <Inventory_Counter.ProgressWindowViewController: 0x1466ea390>.')
})
}
}
My problem is essentially I need a replacement for this line of code.
theProgressWindowController.presentViewController(theProgressWindowController, animated: true, completion: nil)
I forgot to mention here is the code that runs it inside another view controller.
SyncViewController.swift
import UIKit
class SyncViewController: UIViewController {
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.
}
#IBAction func yesSyncButtonAction(sender: UIButton) {
StatusProgress.returningViewControllerIdentifier = "syncWindow"
StatusProgress.runCode = {
print("run code test")
}
StatusProgress.cancelCode = {
print("cancel code test")
}
StatusProgress.show()
}
#IBAction func noSyncActionButton(sender: UIButton) {
tabBarController?.selectedIndex = 1 //assume back to inventory section
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
The biggest problem is that your StatusProgress class is instantiating and showing a view controller. View controllers should instantiate and show other view controllers, model objects should not. So you need to move the logic for presenting the new view controller into you SyncViewController. Then use delegation to communicate to the SyncViewController that the syncing is done.
protocol StatusProgressDelegate {
func statusProgress(status: StatusProgress, shouldShow: Bool)
func statusProgress(status: StatusProgress, shouldCancel: Bool)
}
Your StatusProgress object would have a delegate that conforms to that protocol and call that delegate inside of its show and cancel methods. This means that you need to make the static functions instance methods, and write an initializer for the class so you can instantiate it.
If the view life cycle events are not much important for you, you may just add the view of your progress controller to view of your current controller. or it's even better if you supply the UIView parameter in your show() function.
static func show(attachToView: UIView ){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
attachToView.addSubview(theProgressWindowController.view)
})
}
After all you'd better to remove your progress view from superview
static func cancel(){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
theProgressWindowController.view.removeFromSuperview()
})
}
My application has three viewController associated with three tab of tabBar. I want to switch from 1st tab to 3rd tab and pass some data from 1st view controller to 3rd view controller. I can't do it with segue, because segue create navigation within the selected tab. That is not my requirement. I want to switch tab in tabBar and pass some data without any navigation. How can i do it ?
Swift 3 in latest Xcode version 8.3.3
First you can set a tab bar delegate UITabBarControllerDelegate in FirstViewController. You can use this when you want to send data from one view controller to another by clicking the tab bar button in UITabBarViewController.
class FirstViewController: UIViewController ,UITabBarControllerDelegate {
let arrayName = ["First", "Second", "Third"]
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController.isKind(of: FirstsubsecoundViewController.self as AnyClass) {
let viewController = tabBarController.viewControllers?[1] as! SecondViewController
viewController.arrayData = self.arrayName
}
return true
}
}
Get data in SecondViewController:
class SecondViewController: UIViewController {
var arrayData: [String] = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
print(arrayData) // Output: ["First", "Second", "Third"]
}
}
you can use this code : Objective C
[tab setSelectedIndex:2];
save your array in NSUserDefaults like this:
[[NSUserDefaults standardUserDefaults]setObject:yourArray forKey:#"YourKey"];
and get data from another view using NSUserDefaults like this :
NSMutableArray *array=[[NSUserDefaults standardUserDefaults]objectForKey:#"YourKey"];
swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toTabController" {
var tabBarC : UITabBarController = segue.destinationViewController as UITabBarController
var desView: CaseViewController = tabBarC.viewControllers?.first as CaseViewController
var caseIndex = overviewTableView!.indexPathForSelectedRow()!.row
var selectedCase = self.cases[caseIndex]
desView.caseitem = selectedCase
}
}
Okay, i tried with creating a singleton object in viewController of first tab and then get that object's value from viewController of third tabBar. It works only for once when third tab's view controller instantiates for the 1st time. I never got that singleton object's value in third tab's view controller except the first time. What can i do now ? In my code - In first tab's controller, if i click a button tab will be switched to third tab. Below is my code portion -
In First tab's controller -
#IBAction func sendBtnListener(sender: AnyObject) {
Singleton.sharedInstance.brandName = self.searchDisplayController!.searchBar.text
self.tabBarController!.selectedIndex = 2
}
In Third tab's Controller -
override func viewDidLoad() {
super.viewDidLoad()
//Nothing is printed out for this portion of code except the first time
if !Singleton.sharedInstance.brandName.isEmpty{
println(Singleton.sharedInstance.brandName)
}else{
println("Empty")
}
}
In Singleton Object's class -
class Singleton {
var name : String = ""
class var sharedInstance : Singleton {
struct Static {
static let instance : Singleton = Singleton()
}
return Static.instance
}
var brandName : String {
get{
return self.name
}
set {
self.name = newValue
}
}
}
Edited :
Okay at last it's working. For others who just want like me, please replace all the code from viewDidLoad() to viewWillAppear() method in third tab's (destination tab) controller and it will work. Thanks.