Segmented Control, present childview B from childview A - swift

I'm stuck on how I can show child B after a successful API call from child A. I am using a container view with embedded segues to the 2 views.
The parent view has a segment control that I'm using like this:
#IBAction func segmentsPressed(_ sender: AwardSegmentedControl) {
if sender.selectedSegmentIndex == 0 {
container.segueIdentifierReceivedFromParent("A")
} else {
container.segueIdentifierReceivedFromParent("B")
}
}
I added a function on the parent view called changeView which I'm calling from a successful API call from A.
func changeSubView() {
let oldController = self.storyboard?.instantiateViewController(withIdentifier: "A") as! AwardStoreVC
oldController.willMove(toParentViewController: nil)
oldController.view.removeFromSuperview()
oldController.removeFromParentViewController()
let controller = self.storyboard?.instantiateViewController(withIdentifier: "B") as! AwardsForUserVC
addChildViewController(controller)
view.addSubview(controller.view)
controller.didMove(toParentViewController: self)
}
But nothing happens. Is this the right approach?

Related

Userdefaults Boolean for button

I am fairly new to Swift programming. Using Userdefaults I was trying to customize user behaviour. Below image is of my initial controller. I require to save userdefaults so that App remembers the user selection of button, (i.e. A or B). Can you assist to provide me a function that I use in viewDidLoad and it remembers the button selection and segues to its respective ViewController.
My code to perfrom segue if Button A or B is selected is
let parent = self.storyboard?.instantiateViewController(withIdentifier: "DashboardVC") as! DashboardVC
self.navigationController?.pushViewController(parent!, animated: true)
Yet it doesnt segue. It keeps loading my initial viewcontroller.
do like
set the tag for each button and create the common method for handle the function , for e.g
#IBAction func handle_Action(_ sender: UIButton) {
defaultName.set(sender.tag, forKey: "yourKeyName")
}
and in your class
class ViewController: UIViewController {
let defaultName = UserDefaults.standard
// finally access the integer in your Viewload
override func viewDidLoad() {
super.viewDidLoad()
let getVal = defaultName.integer(forKey: "yourKeyName") as Int
if getVal == 1{ //called by A
}else if getVal == 2{
//called by B
}else{ // not interactwithButton action }
}

Swift - How to use a closure to fire a function in a view model?

I am watching the video series
Swift Talk #5
Connecting View Controllers
url: https://talk.objc.io/episodes/S01E05-connecting-view-controllers
In this video series they remove all the prepareForSegue and use an App class to handle the connection between different view controllers.
I want to replicate this, but specifically only in my current view model; but what I don't get is how to connect view controllers through a view model (or even if you're meant to)
In their code, at github: https://github.com/objcio/S01E05-connecting-view-controllers/blob/master/Example/AppDelegate.swift
They use do this within their view controller
var didSelect: (Episode) -> () = { _ in }
This runs;
func showEpisode(episode: Episode) {
let detailVC = storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewController
detailVC.episode = episode
navigationController.pushViewController(detailVC, animated: true)
}
In the same way, I want to use my ViewController to use my ViewModel for a menu button press (relying on tag).
My code follows;
struct MainMenuViewModel {
enum MainMenuTag: Int {
case newGameTag = 0
}
func menuButtonPressed(tag: Int) {
guard let tagSelected = MainMenuTag.init(rawValue: tag) else {
return
}
switch tagSelected {
case .newGameTag:
print ("Pressed new game btn")
break
}
}
func menuBtnDidPress(tag: Int) {
print ("You pressed: \(tag)")
// Do a switch here
// Go to the next view controller? Should the view model even know about navigation controllers, pushing, etc?
}
}
class MainMenuViewController: UIViewController {
#IBOutlet var mainMenuBtnOutletCollection: [UIButton]!
var didSelect: (Int) -> () = { _ in }
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func mainMenuBtnPressed(_ sender: UIButton) {
let tag = (sender).tag
self.didSelect(tag)
}
}
What I don't understand is how do I connect the command
self.didSelect(tag)
to the function
func menuButtonPressed(tag: Int)
within my ViewModel
As I understand it, according to the swift talk video is that the idea is that the view controller are "plain" and that the view model handles all the major stuff, like menu button presses and then moving to different view controllers as necessary.
How do I connect the didSelect item to my viewModel function?
Thank you.
You should set didSelect property for your controller like here:
func showEpisode(episode: Episode) {
let detailVC = storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewController
detailVC.episode = episode
detailVC.didSelect = { episode in
// do whatever you need
// for example dismiss detailVC
self.navigationController.popViewController(animated: true)
// or call the model methods
self.model.menuButtonPressed(episode)
}
navigationController.pushViewController(detailVC, animated: true)
}

How to detect if a pushed viewcontroller appears again?

assuming I have a viewcontroller (vcA) that pushes QRCodeScannerViewcontroller (vcB). When (vcB) scanned something, It will push ResultviewController (vcC).
-Those 3 views is connected to a UInavigation controller
-the user clicks on the back button on (vcC)
my question is:
1)how can I know if (vcB) is visible without changing code on (vcB)? (vcB) is a pod
2)where will I put this code? I can only access (vcA)
i tried adding this code on (vcA) but nothing happened
override func viewDidDisappear(_ animated: Bool) {
if (vcB.isViewLoaded && (vcB.view.window != nil)){
print("vcb did appear!")
}
}
To know if an instance of cvB's class exists in the navigation stack, you could use this piece of code:
let result = self.navigationController?.viewControllers.filter({
if let vcB = $0 as? UIViewController { // Replace UIViewController with your class, for example ViewControllerB
return true
}
return false
})
if result.isEmpty {
print("An instance of vcB's class hasn't been pused before")
} else {
print("An instance of vcB's class has been pused before")
}

Segue async Firebase data through NavigationController (Swift)

I have been chasing this for two days but yet I am still not sure why my variable isn't being passed in my segue from my login view controller to the chat view controller via the navigation view controller.
I have a button that queries Firebase, checks if the user exists and returns a Firebase query reference for the user. I then want to pass this Firebase query reference when it finishes to the navigation controller's top view controller for use.
Inside my IBAction login button, I have:
var tempUserRef: FIRDatabaseReference?
channelRef.queryOrdered(byChild: "uid").queryEqual(toValue: uid).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
print("uid exist with \(snapshot.childrenCount) number of children")
for s in snapshot.children.allObjects as! [FIRDataSnapshot] {
tempUserRef = self.channelRef.child(s.key)
}
} else {
print("uid didn't exist")
if let name = self.nameField?.text { // 1
tempUserRef = self.channelRef.childByAutoId()
let channelItem = [
"name": name,
"uid": self.uid
]
tempUserRef?.setValue(channelItem)
}
}
self.userRef = tempUserRef
DispatchQueue.main.async {
self.performSegue(withIdentifier: "LoginToChat", sender: tempUserRef)
print("passsed \(self.userRef)")
}
})
Here is my segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if "LoginToChat" == segue.identifier {
if let navVc = segue.destination as? UINavigationController {
if let chatVc = navVc.topViewController as? ChatViewController {
chatVc.senderDisplayName = nameField?.text
if let userRef = sender as? FIRDatabaseReference {
chatVc.userRef = userRef
print("passsing \(self.userRef) to \(chatVc)")
}
}
}
}
super.prepare(for: segue, sender: sender)
}
The print statements all look good on my login controller but when I get to the chat view controller, the userRef is still nil. So my sense is that I have the right segue inputs and handoffs but that the async nature of the data is somehow out of step with my segue.
The chat view controller is using the JSQMessages library if that makes a difference.
Thanks for your help.
EDIT:
Based off feedback I've moved the super.prepare but userRef is still not being set consistently.
SECOND EDIT:
Following paulvs' suggestion, I removed my button segue. However, I did have to create another segue that connected view controller to view controller like this SO question.
Place the call to super.prepare at the end of the function. Otherwise, the segue is performed before you set your variables.

Detail View Controller transition from Master View Contoller

I am getting "unexpectedly found nil while unwrapping an Optional value" because in my code below I am trying to assign value to webview before its initialize. I am trying to transition from Master to Detail view controller.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as NSManagedObject
let controller = (segue.destinationViewController as UINavigationController).topViewController as DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
Detail View Code:
var detailItem: AnyObject? {
didSet {
// Update the view.
self.configureView()
}
}
func configureView() {
// Update the user interface for the detail item.
if let detailContent = detailItem?.valueForKey("content") as? String{
self.webView.loadHTMLString(detailContent as String, baseURL:nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.configureView()
}
It is failing because my Webview in Nil. How do I come around this situation where my outlets are not initialized while setting them.
Please help.
Thanks.
Stop and think about the order in which things happen:
prepareForSegue - The destination view controller exists, but that's all. It has no view and its outlets have not been set. You can set its non-outlet properties but that's all you can do.
The segue starts to happen.
The destination view controller gets viewDidLoad. Now it has a view and its outlets are set.
The segue completes and the destination view controller gets viewWillAppear: and later, viewDidAppear:. Now its view is actually in the interface.
So clearly you cannot permit configureView to assume that the web view exists, because the first time it is called, namely in prepareForSegue, it doesn't exist. configureView needs to test explicitly whether self.webView is nil, and if it is, it should do nothing:
func configureView() {
// Update the user interface for the detail item.
if self.webView == nil { return } // no web view, bail out
if let detailContent = detailItem?.valueForKey("content") as? String{
self.webView.loadHTMLString(detailContent as String, baseURL:nil)
}
}
After that, everything will be fine. viewDidLoad will subsequently be called, and configureView will be called again - and this time, both detailItem and the web view exist, so all will be well.