How can view controller present it's self? - ios

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()
})
}

Related

iOS delegate is nil when trying to pass data back with Swift 5

I know this is a pretty common question but I've tried the various solutions offered here (that are not too old) and in numerous tutorials and I just can't seem to find out why it's still failing for me. Basically setting sendingViewController.delegate to self ends up being nil in sendingViewController. I understand this is very likely because the reference to the sendingViewController is being disposed of. But here is why I'm asking this again.
First, almost every tutorial and every other StackOverflow post is wiring up the mainViewController and the sendingViewController differently. I'm trying to make this work through a Navigation Controller, what one would think is the most common pattern for this.
In the app I'm building (which is more complex than the sample I'm going to show), the mainViewController calls the Settings viewController through a right navbar button. Then the user can select items from a list, which opens a controller with a searchBar and a tableView of items to select from. I need that third view controller to return the selected item from the table view to the settings screen. I'm using storyboards as well. I'm fairly new to Swift and I'm not ready to do all this "programmatically". Any way in the sending view controller, my delegate which should have been set in the calling view controller is nil and I can't invoke the protocol function in the main view controller to pass the data back.
I did a tutorial directly (not using Nav controllers) and I got that to work, but the moment I deviate away, it starts failing. I then put together a streamlined project with two view controllers: ViewController and SendingViewController. ViewController was embedded in a navigation controller and a right bar button was added to go to the SendingViewController. The SendingViewController has a single UI Button that attempts to call the protocol function and dismiss the SendingViewController. I'm not using Seque's, just a simple buttons and protocol/delegate pattern as I can.
My question is what am I missing to actually set the SendingViewController.delegate correctly?
Here's some code:
//ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var showDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func fetchDataButton(_ sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SendingViewController") as! SendingViewController
controller.delegate = self
print("fetching data")
present(controller, animated: true, completion: nil)
}
}
extension ViewController: SendingViewControllerDelegate {
func sendData(value: String) {
print("got Data \(value)")
self.showDataLabel.text = value
}
}
and
// SendingViewController.swift
import UIKit
protocol SendingViewControllerDelegate {
func sendData(value: String)
}
class SendingViewController: UIViewController {
var delegate: SendingViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.navigationController?.popViewController(animated: true)
}
}
Here is a screenshot of the Storyboard:
The ChildViewController does have a storyboard id name of "ChildViewController". All buttons and labels have their appropriate IBOutlet and IBAction's set up.
Help!
i copy paste your code .. its working perfect .. i make just one change
instead of pop you need to use dismiss as you are presenting from your base viewController
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.dismiss(animated: true, completion: nil)
}
here is the project link we.tl/t-NUxm9D26XN
I managed to get this working. In the receiving/parent view controller that needs the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as! sendingViewController
controller.cityDelegate = self
}
Then in the sending view controller in my tableView did select row function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let city = filtered[indexPath.row]
searchBar.resignFirstResponder()
self.navigationController?.popViewController(animated: true)
self.cityDelegate?.addCity(city)
self.dismiss(animated: true, completion: nil)
}
I don't think I should be both popping the view controller and dismissing it, but it works. Also in the view controller I did this:
private var presentingController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentingController = presentingViewController
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
}
}
I don't know if I really need this didMove() or not since it doesn't really do anything.
But some combination of all this got it working.
In my other app I'm not using a navigation bar controller and the standard delegate/protocol method works like a charm.

Presenting the new controller programatically in ios swift only displaying blank screen

I am developing a simple IOS application using swift. In my application I need to open new controller programatically from another controller. So I added another scene to the storyboard.
This is the screenshot
Then I added a new class for new controller which is inheriting from the UIViewController. This is the code for new controller
import UIKit
class ReplayController: 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.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Then, I tried to open the new controller view (ReplayController) from the main controller like this in code.
func gameOver()
{
let replayController = ReplayController()
present(replayController, animated: true, completion: nil)
}
When I call that function, it just pops up the blank screen. Nothing appear on the screen. What is wrong and how can I solve it?
In your Storyboard file, ensure the ReplayController has its 'File's Owner' set to your ReplayController class. Then set the Storyboard ID like below:
Then you can load it like so:
let replay = storyboard?.instantiateViewController(withIdentifier: "ReplayController") as! ReplayController
self.present(replay, animated: true, completion: nil)
You have to reference it with id in storyboard
let replayController = self.storyboard?.instantiateViewController(withIdentifier: "replayControllerID") as! ReplayController
present(replayController, animated: true, completion: nil)
as this line only
let replayController = ReplayController()
doesn't load the xib or storyboard object associated with the VC
When you design your view controllers in storyboard, you need to programatically create them using the method instantiateViewController of class UIStoryboard to access it along with all the outlets.
Before that, you need to set the Storyboard ID of the respective view controller in your storyboard like this:
And then present it:
func gameOver() {
if let myVC = storyboard?.instantiateViewController(withIdentifier: "ViewController") {
self.present(myVC, animated: true, completion: nil)
}
}

Previous view controller with delegate relationship does not take expected action

I have a todo list that allows the user to add todos to a table view. On a separate view controller (CompletedViewController) the user can see previously-completed todos.
There is an itemBarButton in the CompletedViewController that should allow the user to clear the list of completed items, as well as clear the array of completed todos (completedThings) on the initial ViewController.
Creating an instance of CompletedViewController and setting the completedTodos array in ViewController.swift:
#IBAction func viewCompletedTapped(_ sender: Any) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "Completed") as? CompletedViewController {
vc.completedTodos = completedThings
navigationController?.pushViewController(vc, animated: true)
}
}
the protocol in CompletedViewController.swift:
protocol CompletedCleared {
func didClearCompleted()
}
the method called when clicking the 'clear' itemBarButton in CompletedViewController.swift
#objc func clearCompleted() {
completedTodos = []
tableView.reloadData()
let vc = ViewController()
vc.didClearCompleted()
}
conforming to the protocol in ViewController.swift
func didClearCompleted() {
completedThings.removeAll()
}
This does not clear the list on the previous view controller. What am I doing wrong?
You forgot to set the delegate of the CompletedViewController. In viewCompletedTapped add this line after instantiating the view controller:
vc.delegate = self
Also make sure you use this delegate method in CompletedViewController. clearCompleted should probably look like this:
#objc func clearCompleted() {
completedTodos = []
tableView.reloadData()
delegate?.didClearCompleted()
}

Performing a segue on a view controller that is nested inside of another view controller in swift

I have a view controller which is nested inside of another view controller using a container view. Is it possible for me to segue from the view which is currently in the container view and replace it with another view controller in the same container view. I.e. the content that is around the container view is not removed by another view controller taking up the entire view.
Yes it is. You can read about that in the Apple Docs.
Considering your containerView currently only has one viewcontroller, here is a very basic example:
func loadVCWithId(idToLoad: String){
childViewControllers[0].willMoveToParentViewController(nil)
childViewControllers[0].view.removeFromSuperview()
childViewControllers[0].removeFromParentViewController()
let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier(idToLoad)
UIView.transitionWithView(yourContainer, duration: 0.5, options: UIViewAnimationOptions.TransitionFlipFromRight, animations: {self.yourContainer.addSubview((secondViewController?.view)!)}, completion: nil)
secondViewController!.view.frame = firstContainer.bounds
// do initialization of secondViewController here
secondViewController?.didMoveToParentViewController(self)
}
loadVCWithId(idToLoad:String)is a method within your host viewcontroller.
In this code fragment I delete the current content of the container (probably not the best way to just access index 0, but for the sake of this example, this should be enough), instantiate a new ViewController by ID (this one is present in my storyboard but not accessbile yet), animate the transition and actually add the new VC to the container.
Hope this helps.
this my solution maybe helpful for
first i create a protocol on childViewController
protocol ChildViewControllerDelaget
{
func performForSegue(SegueIdentifier:String)
}
class ChildViewController: UIViewController {
var delaget:ChildViewControllerDelaget?
override func viewDidLoad() {
super.viewDidLoad()
}
init()
{
}
#IBAction func myAction(sender: AnyObject) {
if delaget != nil {
deleget.performForSegue("mySegueIdentifier")
}
}
and on MainViewController
class ViewController: UIViewController,ChildViewControllerDelaget {
override func viewDidLoad()
{
super.viewDidLoad()
let child = ChildViewController()
child.delaget = self
}
func performForSegue(segueIdentifier:String)
{
self.performSegueWithIdentifier(segueIdentifier, sender: nil)
}
}

swift delegate beetween two view controller without segue

My delegate protocol never called
My first controller - ViewController
class ViewController: UIViewController,testProtocol {
#IBAction func btInit(sender: AnyObject) {
println("Bt Init")
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initViewController: UIViewController = storyBoard.instantiateViewControllerWithIdentifier("viewTarget") as targetViewController
self.presentViewController(initViewController,animated: false, nil)
}
var targetController = targetViewController();
override func viewDidLoad() {
super.viewDidLoad()
self.targetController.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func testDelegate(){
println(" in my view controller delegate ")
}
}
In my second view controller - targetViewController
protocol testProtocol {
func testDelegate() // this function the first controllers
}
class targetViewController: UIViewController {
#IBAction func BtTarget(sender: AnyObject) {
println("bt target pressed")
delegate?.testDelegate()
}
var delegate : testProtocol?
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func testDelegate(){
println(" in my target view controller delegate ")
}
}
Why is testDelegate() never called on ViewController? What am I doing wrong? Thanks.
I have read a lot of posts about this, but all of the examples are given with segue transition, and I don't want use a segue.
Typically you set a new view controller's delegate property in prepareForSegue:. You said you're not using a segue, so you'll need to instantiate the second view controller and present it somehow. You can do this by doing something like:
let storyboard = UIStoryboard(name: "AStoryboardName", bundle: nil)
let secondVC = storyboard.instantiateViewControllerWithIdentifier(anIdentifier) as! targetViewController
secondVC.delegate = self
presentViewController(secondVC, animated: true, completion: nil)
You have a testDelegate() method in both view controllers, but you only want it in the first view controller. Then your second view controller can call delegate?.testDelegate() at the appropriate time.
Finally, you typically want to make delegate properties weak, so I would recommend changing var delegate : testProtocol? to weak var delegate: testProtocol?
I would read up on delegation. Here is a relatively simple 5 step process to delegation that may help you:
Delegation in 5 Steps:
object A is the delegate for object B, and object B will send out the messages:
Define a delegate protocol for object B.
Give object B an optional delegate variable. This variable should be weak.
Make object B send messages to its delegate when something interesting happens, such as the user pressing the Cancel or Done buttons, or when it needs a piece of information.
Make object A conform to the delegate protocol. It should put the name of the protocol in its class line and implement the methods from the protocol.
Tell object B that object A is now its delegate (in prepareForSegue(sender)).

Resources