I present my secondViewController from (attendanceViewController) and in dismiss completion I'm trying to pass parameters and call functions. The AttendanceViewController appears and the function is called. The problem is that all the Objects are nil when dismiss(#IBOutlet weak var tableView: UITableView! , #IBOutlet weak var boxTypeSKU: UIView!....all)
self.presentingViewController!.dismissViewControllerAnimated(true, completion: { _ i
let attView: AttendanceViewController = self.storyboard!.instantiateViewControllerWithIdentifier("AttendanceViewID") as! AttendanceViewController
attView.currAttendance = self.currAttendance
attView.searchProductWithSKU("\(sku)")
})
I solved my problem using Protocols like this tutorial (http://swiftdeveloperblog.com/pass-information-back-to-the-previous-view-controller/) I think it's more elegant and efficient.
There's my updated code:
In second view Controller (BarcodeScannerViewController.swift) I do it:
protocol BarcodeScannerProtocol {
func setSKUScanner(sku: String)
}
class BarcodeScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
var delegate:BarcodeScannerProtocol?
func back() {
let sku = (barcode as NSString).substringWithRange(NSMakeRange(6, 8))
delegate?.setSKUScanner(sku)
self.presentingViewController!.dismissViewControllerAnimated(true, completion: { _ in
}
}
In first view controller (AttendanceViewController.swift):
class AttendanceViewController: UIViewController, BarcodeScannerProtocol {
var strSKUScanner : String?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if let skuScanned = strSKUScanner {
searchProductWithSKU(skuScanned)
} else {
fetchProducts()
}
}
// MARK: BarcodeScannerProtocol functions
func setSKUScanner(sku: String) {
self.strSKUScanner = sku
}
}
The first thing to be noticed is that a new instance of AttendanceViewController is being instantiated. This means that the properties are not being set on the correct object. There needs to be a reference to the view controller that presented the secondViewController. How that is done is up to you, but I recommend a callback containing the currAttendance variable. This would be a property on the presented view controller. Once the callback is called by the presented view controller, the parent AttendanceViewController can set its own property and dismiss the presented view controller and call the searchProductWithSKU(_:) method.
Related
This is the first time I am working with SwiftUI, and I have searched for this problem but did not find any working solution, so I would appreciate all help I can get.
I have a UIViewController, in which I present a SwiftUI View through UIHostingController. What I want to achieve is that when I press the button in my SwiftUI view, the action is going to trigger the delegate in UIViewController, alternatively, trigger a function in UIViewController and then from that function trigger the delegate.
class MyViewController: UIViewController {
public var delegate: MyViewControllerDelegate?
let facialView = UIHostingController(rootView: FacialTutorial())
override func viewWillAppear(_ animated: Bool) {
addChild(facialView)
view.addSubview(facialView.view)
setupConstraints()
}
extension MyViewController {
#objc private func buttonPressed() {
delegate?.buttonPressed()
}
}
}
And in my SwiftUI view FacialTutorial
struct FacialTutorial: View {
var body: some View {
VStack() {
Button {
// I want to call delegate?.buttonPressed() action here
} label: {
Text("Press button")
}
}
}
}
EDIT
Okay to be more clear, my ViewController is configuring the page differently for a number of cases. So in practice, I do not initiate the SwiftUI view from viewWillAppear. Rather this is how I do
public var delegate: MyViewControllerDelegate?
let facialView = UIHostingController(rootView: FacialTutorial())
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addChild(facialView)
configureSubviews()
}
private func configureForInstructionMode() {
view.addSubview(facialUIView.view)
setupConstraints()
}
I must have it this way because I need to configure the view differently depending on which mode I am going to configure for. When I declare the facialView inside the viewDidLoad or viewWillAppear, I cannot access the instance in configureForInstructionMode(), or it's value is nil..
You simply need to pass a reference to your MyViewController instance through to MyUIView (Which should probably be MyView since it isn't a subclass of UIView) via its initialiser. You could use a delegation pattern or you could pass a closure and then invoke the delegate method from the closure. The second way is more "Swifty".
(I have moved the code to viewDidLoad since if you have it in viewWillAppear the view may be added multiple times if the view controller appears and disappears and appears again)
class MyViewController: UIViewController {
public var delegate: MyViewControllerDelegate? {
weak var myView: UIHostingController<MyUIView>!
override func viewDidLoad() {
super.viewDidLoad()
let myView = UIHostingController(rootView: MyUIView(buttonHandler: { [weak self] in
self?.delegate?.buttonPressed()
}))
self.myView = myView
view.addSubview(myView.view)
setupConstraints()
}
}
Then, in your SwiftUI view you can declare a property to hold the closure and invoke that closure in your button
struct MyUIView: View {
var buttonHandler: (()->Void)?
var body: some View {
VStack() {
Button {
buttonHandler?()
} label: {
Text("Press button")
}
}
}
}
Usually, I write this function to init my presenter from viewController. But I want to use init() for making this. What can I do?
ViewController :
class ViewController: UIViewController {
private let viewPresenter = viewPresenter()
override func viewDidLoad() {
self.viewPresenter.attachView(controller: self)
}
#IBAction func faceBookButtonTapped(sender: UIButton) {
self.viewPresenter.showLoginWindow()
}
}
ViewPresenter :
class ViewPresenter {
var controller: ViewController?
func attachView(controller: ViewController) {
self.controller = controller
}
}
So, If I make this Init in my presenter :
let controller: ViewController?
init (controller:ViewController) {
self.controller = controller
}
And try to init like this in viewController:
private let viewPresenter = ViewPresenter(controller: self)
Xcode give me this error:
Cannot convert value of type '(NSObject) -> () -> ViewController' to expected argument type 'ViewController'
the problem is that there is no "self" at that point in time where you want to initialize your presenter because your view controller is initialized later. You could work around this problem by declaring your presenter as lazy like this
fileprivate lazy var viewPresenter: ViewPresenter = {
return ViewPresenter(controller: self)
}()
or you could just initialize your presenter later in viewDidLoad
private var viewPresenter: ViewPresenter!
override func viewDidLoad() {
super.viewDidLoad()
self.viewPresenter = ViewPresenter(controller: self)
}
If you ask me the best approach is (as you already did) to attach the view in viewDidLoad to the presenter.
Hope this helps.
I want to update the label in my DashboardViewController from my AccountViewController when the back button is pressed in AccountViewController.
I have tried passing back a variable from 2nd view to 1st view and updating the label in viewDidLoad and in viewWillAppear but it never updates the label when the 1st view is back on screen.
I tried creating a function in 1st view to update the label with a string passed into the function and calling that function from 2nd view but it says that the label is nil so it couldn't be updated.
My latest attempt was to create a delegate but that didn't work either.
Here is my delegate attempt.
class DashboardViewController: UIViewController, AccountViewControllerDelegate {
#IBOutlet weak var welcome_lbl: UILabel!
func nameChanged(name: String){
var full_name = "Welcome \(name)"
welcome_lbl.text = "\(full_name)"
}
override func viewDidLoad() {
super.viewDidLoad()
AccountViewController.delegate = self
}
}
And then in my AccountViewController I have this
protocol AccountViewControllerDelegate{
func name_changed(name: String)
}
class AccountViewController: UIViewController, UITextFieldDelegate {
var info_changed = false
static var delegate: AccountViewControllerDelegate!
#IBAction func back_btn(sender: AnyObject) {
if(info_changed){
AccountViewController.delegate.name_changed(name_tf.text!)
}
self.dismissViewControllerAnimated(true, completion: nil)
}
Did I mess up the delegate process somehow ? Or is there an easier way to do this?
First. Your delegate should be a normal property of AccountViewController. There is no need to update your name when user press back. You can change DashboardViewController`s name when user change name in AccountViewController. When user go back to DashboardViewController. It`s already show the changed name.
protocol AccountViewControllerDelegate{
func name_changed(name: String)
}
class AccountViewController: UIViewController, UITextFieldDelegate {
var delegate: AccountViewControllerDelegate?
// when user change name through textfield or other control
func changeName(name: String) {
delegate?.name_changed(name)
}
}
Second. When DashboardViewController show AccountViewController. I think it should be push. Set DashboardViewController instance be AccountViewController instance`s delegate.
class DashboardViewController: UIViewController, AccountViewControllerDelegate {
#IBOutlet weak var welcome_lbl: UILabel!
func nameChanged(name: String){
var full_name = "Welcome \(name)"
welcome_lbl.text = "\(full_name)"
}
// present or push to AccountViewController
func showAccountViewController {
let accountViewController = AccountViewController()
accountViewController.delegate = self
// do push view controller
}
}
I have 2 controllers
and have got 1 global variable, the problem is if I go to controller 2 and click on button northAmericaClick, it will navigate back to control 1, but the value of global variable won't change!
this is my code
controller 1
class OurViewController: UIViewController {
#IBOutlet weak var menuButton: UIBarButtonItem!
#IBOutlet weak var selectedServer: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
selectedServer.setTitle(selected server, forState: UIControlState.Normal) // selected server this is global variable
}
controller 2
class selectServerController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func northAmericaClick(sender: AnyObject) {
selectedserver = "North America"
self.navigationController?.popViewControllerAnimated(true)
}
From
You need to use a delegate. Here is an example how do use a delegate in Swift.
On your first ViewController, set your delegate when you load the second VC:
For example, if you are using the Storyboard Editor:
var secondViewController = (segue.destinationViewController.visibleViewController as MySecondViewControllerClass)
secondViewController.delegate = self
Write a Protocol and define a func to write you values back
For example, create a file called "Protocol.swift" and write something like that:
protocol writeValueBackDelegate {
func writeValueBack(value: String)
}
Add the function to your FirstViewController
func writeValueBack(value: String) {
// this is my value from my second View Controller
}
And to your ViewControllerClass
class ViewController: UIViewController, writeValueBackDelegate
Go to the Second View Controller, and add the delegate here:
class SecondViewController: ViewController {
// delegate for FirstViewController
var delegate: writeValueBackDelegate?
On your Second View Controller, you can now use this to call the func in the first View Controller an pass data.
delegate?.writeValueBack("That is a value")
You also need to indicate that your first view controller implements the protocol: class ViewController: UIViewController, writeValueBackDelegate {
A part of doing it with delegate you also can create singleton class ViewControllersDataModel class and share the variable using it:
import Foundation
class ViewControllersDataModel {
static let sharedInstance = ViewControllersDataModel()
var selectedserver: String = ""
private init() {
}
}
And call it like this:
ViewControllersDataModel.sharedInstance.selectedserver = "Selected Option";
Ok, I can do this with this code, only check when viewWillDisapear and call the parent of this view controller in the navicationController:
override func viewWillDisappear(animated: Bool) {
if ((self.navigationController!.viewControllers.last?.isKindOfClass(ActivityMyViewController)) == true){
let backView:MyViewController = self.navigationController!.viewControllers.last as! MyDetailViewController
backView // do whatever you want
}
}
I hope this code can help you, good luck
thanks guys for helping ;)
it was very simple
i just use then when it comeback ^^"
override func viewWillAppear(animated: Bool) {
selectedServer.setTitle(selectedserv, forState: UIControlState.Normal)
}
I would like to make a UI that have label, table view and one button click. When click on the button, we pop up a half screen view that have lots of buttons. I want user can still click on the rest of the screen also.
So i use the approach that suggest in the post
How To Present Half Screen Modal View?
Method 2: to animate a UIView which is of size half of the existing view.
Then you have to simply follow animation of the UIView.
Here as it is just a UIView that will be added as subview to existing view, you will be able to touch the rest of the screen.
As i am newbie to the ios and swift, I would like to get some suggestions.
Now i am successfully add as subview and show in the half of the screen.
How can i implement to let subview click button result show on parent view label text?
I am thinking about parent.xib and subview.xib have the same UIVeiwController.swift. Then i can #IBOutlet and #IBAction to the same controller swift file and update the result. But don't know it is the accpetable way to do?
If not, how can the subViewController send result/event to the parent view and update in the parent view component?
You could use delegation. This keeps your view controllers decoupled, i.e. prevents the child from having a reference to its parent, which allows other view controllers to interact with the modal view controller in the same way.
class ParentViewController : UIViewController, ModalViewControllerDelegate {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let modalViewContorller = ModalViewController()
modalViewContorller.delegate = self
self.presentViewController( modalViewContorller, animated: true, completion: nil )
}
func modalViewControllerDidProduceResult( modalViewController: ModalViewController, result: String ) {
self.label.text = result
}
}
protocol ModalViewControllerDelegate {
func modalViewControllerDidProduceResult( modalViewController: ModalViewController, result: String )
}
class ModalViewController: UIViewController {
var delegate: ModalViewControllerDelegate?
#IBAction func buttonClicked( sender: AnyObject? ) {
delegate?.modalViewControllerDidProduceResult( self, result: "Hello!" )
}
}
You could also use a closure, which in Swift provides a more concise syntax.
class ParentViewController : UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let modalViewContorller = ModalViewController()
self.presentViewController( modalViewContorller, animated: true, completion: nil )
modalViewContorller.resultBlock = { (result: String) in
self.label.text = result
}
}
}
class ModalViewController: UIViewController {
var resultBlock: ((String) -> ())?
#IBAction func buttonClicked( sender: AnyObject? ) {
self.resultBlock?( "Hello!" )
}
}