How to HIDE View when tap on button swift? - swift

In first view controller we have two buttons
if we tap on first view controller oneButn i need to hide onebutnContainerView in secondview controller
if we tap on first view controller secndButn i need to hide twobutnContainerView in secondview controller
in first view controller viewController.oneButnContainerView.isHidden = true getting error:
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
first view controller code:
class firstViewController: UIViewController{
#IBAction func oneButn(_ sender: UIButton) {
self.view.endEditing(true)
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "NewZoomAddressViewController") as! NewZoomAddressViewController;
viewController.delegate = self
viewController.oneButnContainerView.isHidden = true
viewController.twobutnContainerView.isHidden = false
self.navigationController?.pushViewController(viewController, animated: true);
}
#IBAction func secndButn(_ sender: UIButton) {
self.view.endEditing(true)
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "NewZoomAddressViewController") as! NewZoomAddressViewController;
viewController.delegate = self
viewController.oneButnContainerView.isHidden = false
viewController.twobutnContainerView.isHidden = true
self.navigationController?.pushViewController(viewController, animated: true);
}
}
I have outlets for two views in Second view controller
#IBOutlet weak var oneButnContainerView: UIView!
#IBOutlet weak var twoButnContainerView: UIView!
how to hide seconviewcontroller view in firstviewcontroller

That's because you are trying to hide a View that wasn't initialized yet. As a rule of thumb, keep in mind that when you instantiate a viewController, you can only access its data not its views. You have 2 ways of fixing this:
Create 2 variables inside secondViewController:
var isOneButnContainerViewHidden: Bool = false
var isTwoButnContainerViewHidden: Bool = false
Assign a value to those 2 variables inside firstViewController:
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "NewZoomAddressViewController") as! NewZoomAddressViewController;
viewController.delegate = self
viewController.isOneButnContainerViewHidden= false
viewController.isTwoButnContainerViewHidden= true
self.navigationController?.pushViewController(viewController, animated: true);
Now inside your secondViewController's viewDidLoad or viewWillAppear, hide/show your buttonContainerViews based on the value of the 2 variables created:
oneButnContainerView.isHidden = isOneButnContainerViewHidden
twoButnContainerView.isHidden = isTwoButnContainerViewHidden
The second way involves forcing the viewController to layout its views by calling loadViewIfNeeded() on the secondViewController before accessing its views (in this case you are trying to hide/show the views).

Related

How to pass data of one label(mainStoryBoard) to another label(of second storyboard)

I have a label1 in a view controller of main.storyboard
i have another label2 in view controller of storyboard named Second.storyboard
how can I pass data from one storyboard to another all solutions I can find are about passing data within the same storyboard.Help me with my question.
make a Global variable in Second View Controller
var labelText = ""
While Pushing to secondViewController from First Controller
let secondVC = secondViewController()
secondVC.labelText = label.text // Pass label text from first VC
self.navigationController?.pushViewController(secondVC, animated: true)
Create the instance of Storyboard with the relevant name while creating the instance of SecondViewController in FirsViewController, i.e
class FirsViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
func pushVC2(_ sender: UIButton) {
if let controller = UIStoryboard(name: "Storyboard-2", bundle: nil).instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController { //here....
controller.str = "Your_Text_Here"
self.navigationController?.pushViewController(controller, animated: true)
}
}
}
Your SecondViewController looks like,
class SecondViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
var str: String?
override func viewDidLoad() {
super.viewDidLoad()
self.label2.text = str
}
}

UIHostingController and navigationController

Default swift project with view controller embedded in navigationController and pushing to next UIHostingController.
How to call navigationController?.popViewController from SimpleView ?
ViewController:
#IBOutlet weak var button: UIButton?
override func viewDidLoad() {
super.viewDidLoad()
button?.addTarget(self,
action: #selector(showNewSwiftUIController),
for: .touchUpInside)
}
#objc func showNewSwiftUIController() {
let vc = UIHostingController(rootView:SimpleView())
navigationController?.pushViewController(vc, animated: true)
}
SwiftUI:
struct SimpleView: View {
var body: some View {
Text("SimpleView")
.foregroundColor(.black)
}
}
First.
Have you tried?
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode> // reference
Then on your body call:
self.presentationMode.wrappedValue.dismiss()
If that does not work:
I will suggest you either pass the navigation controller as an optional variable into the View as a reference and access from there.
Other option and maybe a healthier option is to pass a "callback" variable from the parent to the childView to dismiss the view (you need to save the Navigation as a variable to keep track)
Last option (Not recommended)
if var topController = UIApplication.shared.windows.first?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
topController.navigationController.popViewController(animated:true)
}

dimiss modal and return to presented childViewController in containerView

I am having a bit of an issue with dismissing a modal view presented from a childviewController in a container view. I have a UINavigationController as the rootViewController (MainNavigationController), and present a modal from one of the childViewControllers from the selectedSegmentIndex 1 (secondViewController). The modal is presented fine, but when I dismiss the modal to go back to the secondViewController(a subclass of HomeController) it returns me back to selectedIndex 0, so not the selectedIndex 1 childViewController it was presented from. I would like the modal to dismiss and return the user back to childViewController it was presented from (the secondViewController) and not return back to selectedIndex 0. Thanks in advance!
// NavigationConroller as rootViewController
class MainNavigationController: UINavigationController {
var segmentedController: UISegmentedControl!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
viewControllers = [homeController]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.isLoggedIn()
}
func showLoginController() {
let loginController = LoginController()
present(loginController, animated: true, completion: {
// perhaps do something here later
})
}
}
// HomeController as parentViewController
class HomeController: UIViewController, FBSDKLoginButtonDelegate {
// child view controllers to put inside content view
var firstViewController: TravelersFeedVC?
var secondViewController: ProfileVC?
private var activeViewController: UIViewController? {
didSet {
removeInactiveViewController(inactiveViewController: oldValue)
updateActiveViewController()
}
}
private func removeInactiveViewController(inactiveViewController: UIViewController?) {
if let inActiveVC = inactiveViewController {
// call before removing child view controller's view from hierarchy
inActiveVC.willMove(toParentViewController: nil)
inActiveVC.view.removeFromSuperview()
// call after removing child view controller's view from hierarchy
inActiveVC.removeFromParentViewController()
}
}
private func updateActiveViewController() {
if let activeVC = activeViewController {
// call before adding child view controller's view as subview
addChildViewController(activeVC)
activeVC.view.frame = contentView.bounds
contentView.addSubview(activeVC.view)
// call before adding child view controller's view as subview
activeVC.didMove(toParentViewController: self)
}
}
// UI elements
lazy var contentView: UIView = {
let tv = UIView()
tv.backgroundColor = UIColor.purple
tv.translatesAutoresizingMaskIntoConstraints = false
tv.layer.masksToBounds = true
return tv
}()
var segmentedController: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
activeViewController = firstViewController
checkIfUserIsLoggedIn()
view.addSubview(contentView)
setupProfileScreen()
let items = ["Travelers", "Me"]
segmentedController = UISegmentedControl(items: items)
navigationItem.titleView = segmentedController
segmentedController.tintColor = UIColor.black
segmentedController.selectedSegmentIndex = 0
// Add function to handle Value Changed events
segmentedController.addTarget(self, action: #selector(HomeController.segmentedValueChanged(_:)), for: .valueChanged)
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Sign Out", style: .plain, target: self, action: #selector(handleSignOut))
navigationItem.leftBarButtonItem?.tintColor = UIColor.black
}
// reference to collectionViewController
var travelersFeedVC: TravelersFeedVC!
func segmentedValueChanged(_ sender:UISegmentedControl!)
{
switch segmentedController.selectedSegmentIndex {
case 0:
activeViewController = firstViewController
case 1:
activeViewController = secondViewController
default: // Do nothing
break
}
}
// secondViewcontroller in containerView where modal is presented from
class ProfileVC: UIViewController {
// button to present modal
lazy var placesButton: UIButton = {
let customButton = UIButton(type: .system)
customButton.backgroundColor = UIColor.clear
// customButton.frame = CGRect(x: 150, y: 50, width: 120, height: self.view.frame.height)
customButton.setTitle("## of Places", for: .normal)
customButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
customButton.setTitleColor(.white, for: .normal)
customButton.addTarget(self, action: #selector(handleShowPlacesVC), for: .touchUpInside)
return customButton
}()
// function to call to present modal
func handleShowPlacesVC() {
let placesVC = PlacesTableVC()
let navigationController = UINavigationController(rootViewController: placesVC)
present(navigationController, animated: true, completion: nil)
}
// modal view to dismiss
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "back", style: .plain, target: self, action: #selector(handleCancel))
}
// dismiss modal view to return to secondViewController in childViewController containerView
func handleCancel() {
dismiss(animated: true, completion: nil)
}
When closing the modal dialog the viewDidAppear function in MainNavigationController is called. There you set a new homeController with it's childs. This will trigger a viewDidload in the HomeController with setting of firstViewController. Try to set a breakpoint there and you will see it.
I suggest to avoid content creation in viewDidAppear, use viewDidLoad instead.
Another hint: 'dismiss' is defined as: 'Dismisses the view controller that was presented modally by the view controller.' - If you open for instance an alert above your modal vc it closes the alert, not the modal view (self). A correct implementation has to call dismiss on the presenting controller (same controller that opened it): "presentingViewController?.dismiss()"
It works in your code because apple has implemented a fallback for the case that nothing is presented, but it's a trap that cause some headache sometime.
The chances are that although you're calling present from the child view controller, it isn't in fact handling the presentation. From the Apple docs:
The object on which you call this method may not always be the one that handles the presentation. Each presentation style has different rules governing its behavior. For example, a full-screen presentation must be made by a view controller that itself covers the entire screen. If the current view controller is unable to fulfill a request, it forwards the request up the view controller hierarchy to its nearest parent, which can then handle or forward the request.
Since you're keeping a reference of the active view controller, one solution may be to explicitly set the index upon dismissal.

Need to free memory after Modal Segues, but I need both my Segues to pass data from A to B and B to A

I am making a game for iOS with SpriteKit.
I have 2 Viewcontrollers. One is the GameViewController and the other one is the MenuViewController. Let's call them A and B respectively.
When the player dies, a function is called in GameScene.swift that launches a modal "Lost" Segue to B. There, the player can restart the game or buy a life and a "Back" Segue is called to A.
I need to dismiss the additional Views that get created each time I call a segue.
Problem is: I need the "Lost" Segue to send data about the Score to View B and I need the "Back" Segue to send data to View A about wether or not the player used a life.
I have implemented all this. But now I need to find how to dismiss old views that keep eating the device's memory, thus leading to lag and crash.
I have googled for hours and hours. No solution was adapted to my situation.
The solutions I found either caused my app to bug, data not to be passed or views not to be generated.
I will not add code here since there is a LOT. But I am sure the answer is actually really easy, just not for a beginner like me.
I think a possible solution would be an unwind segue from B to A ?
But do unwind segues pass data along ?
Moreover, I found no answer I could understand on how to use an unwind segue.
I exhausted all my possibilities. Stack Exchange is my last chance.
You definitely should use an unwind segue to return to the previous viewController, otherwise as you have found your memory usage increases until your apps quits.
I created the following example from your description. It uses a standard segue to move from the GameViewController to the MenuViewController and it uses an unwind segue to move from the MenuViewController back to the GameViewController.
The GameViewController has a Player Dies UIButton, a UITextField for entering a score, and a UILabel for displaying the lives.
The MenuViewController has a UILabel for showing the score, a Buy a Life UIButton for adding lives, and a Restart UIButton for returning to the GameViewController.
Here's the code:
GameViewController.swift
import UIKit
class GameViewController: UIViewController {
#IBOutlet weak var scoreTextField: UITextField!
#IBOutlet weak var livesLabel: UILabel!
var lives = 3
func updateLivesLabel() {
livesLabel.text = "Lives: \(lives)"
}
override func viewDidLoad() {
super.viewDidLoad()
updateLivesLabel()
}
// This is the function that the unwind segue returns to.
// You can call it anything you want, but it has to be in
// the viewController you are returning to, it must be tagged
// with #IBAction and it must take a UIStoryboardSegue as its
// only parameter.
#IBAction func returnFromMenu(segue: UIStoryboardSegue) {
print("We're back in GameViewController")
// Update the lives label based upon the value passed in
// prepareForSegue from the MenuViewController.
updateLivesLabel()
}
#IBAction func goPlayerDies(sender: UIButton) {
lives--
self.performSegueWithIdentifier("Lost", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Lost" {
let destinationVC = segue.destinationViewController as! MenuViewController
destinationVC.score = Int(scoreTextField.text ?? "") ?? 0
destinationVC.lives = lives
}
}
}
MenuViewController.swift
import UIKit
class MenuViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var score = 0
var lives = 0
override func viewDidLoad() {
super.viewDidLoad()
scoreLabel.text = "Score: \(score)"
}
#IBAction func buyLife(sender: UIButton) {
lives++
}
#IBAction func goRestart(sender: UIButton) {
self.performSegueWithIdentifier("Back", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Back" {
let destinationVC = segue.destinationViewController as! GameViewController
destinationVC.lives = lives
}
}
}
This is how you wire up the forward segue to be called programmatically:
Control-drag from ViewController icon to the MenuViewController:
Select Present Modally from the pop-up:
Click on the segue arrow between the viewControllers and give it an identifier in the Attributes Inspector:
This is how you wire up the unwind segue to be called programmatically:
Control-drag from ViewController icon to Exit icon:
Choose returnFromMenu from pop-up:
Click on the Unwind Segue in the Document Outline and give it the identifier "Back" in the Attributes Inspector on the right:
Alternate Answer
Instead of using segues, you can present and dismiss viewControllers manually. The advantage for your app is that the MenuViewController will be allocated only once and will persist for the life of the app. This same viewController will be presented and dismissed repeatedly, but it will not be deallocated which I suspect is leading to your crashes.
The GameViewController will be the initialViewController that is created by the Storyboard. The MenuViewController will be loaded in viewDidLoad of the GameViewController.
To make this work, you need to add an identifier to the MenuViewController so that it can be instantiated by name. Click on the MenuViewController in the Storyboard and set its Storyboard ID in the Identity Inspector:
Here is the code. Note that all mention of segues is gone. Note how viewWillAppear is used to update the viewControllers.
GameViewController.swift
import UIKit
class GameViewController: UIViewController {
#IBOutlet weak var scoreTextField: UITextField!
#IBOutlet weak var livesLabel: UILabel!
var menuViewController: MenuViewController?
var lives = 3
func updateLivesLabel() {
livesLabel.text = "Lives: \(lives)"
}
override func viewDidLoad() {
super.viewDidLoad()
menuViewController = self.storyboard!.instantiateViewControllerWithIdentifier("MenuViewController") as? MenuViewController
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
updateLivesLabel()
}
#IBAction func goPlayerDies(sender: UIButton) {
lives--
menuViewController?.score = Int(scoreTextField.text ?? "") ?? 0
menuViewController?.lives = lives
self.presentViewController(menuViewController!, animated: true, completion: nil)
}
}
MenuViewController.swift
import UIKit
class MenuViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var score = 0
var lives = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
scoreLabel.text = "Score: \(score)"
}
#IBAction func buyLife(sender: UIButton) {
lives++
}
#IBAction func goRestart(sender: UIButton) {
let destinationVC = self.presentingViewController as! GameViewController
destinationVC.lives = lives
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}

Increase a counter in the first interface controller by pressing a button in the second interface controller

I would like to increase the value of the property in the first interface controller in the IBAction (Add1) method of the second interface controller
and then use the value of this property to update the label when the first view controller is activated
I was able to increase the value , but the value increases even if I press the back button.
I need to find a solution so that when I press the IBAction in the second interface controller , I can get that result and use it in the first interface controller and update the label.
here is the code:
First interface controller:
Blockquote
#IBOutlet weak var resultButtonLabel: WKInterfaceButton!
#IBAction func resultButton() {
pushControllerWithName("secondInterfaceController", context: self)
}
override func willActivate() {
super.willActivate()
resultButtonLabel.setTitle("\(counter++)")
}
Blockquote
Second interface controller:
Blockquote
var counter = 1
#IBAction func weScored() {
counter++
popController()
}
Blockquote
The easy way to implement this idea is use of NSUserDefaults
In your firstViewController you can read values form NSUserDefaults this way:
override func viewDidLoad() {
super.viewDidLoad()
let score = NSUserDefaults().integerForKey("Score")
resultButtonLabel.text = "\(score)"
}
and into your SecondViewController you can increase this counter with NSUserDefaults this way:
import UIKit
class SecondViewController: UIViewController {
var counter = Int()
override func viewDidLoad() {
super.viewDidLoad()
counter = NSUserDefaults().integerForKey("Score")
}
#IBAction func weScored(sender: AnyObject) {
counter++
NSUserDefaults().setInteger(counter, forKey: "Score")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("ViewController") as! UIViewController
self.presentViewController(vc, animated: true, completion: nil)
}
}
Hope this will help.