I have 2 UIViewControllers and I try to hide an UILabel from the second UIViewController using Notifications and Observer.
Is the first time when I use this design pattern and I'm a little bit confused. What I'm doing wrong ?
I want to specify that I'm getting the message from that print for the first time only when I click the back button from the second ViewController.
And after that I'm getting the message normal when I click Go Next but the UILabel is not hidden or colour changed.
Here is my code for first UIViewController:
class ReviewPhotosVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.post(name: Notification.Name("NotificationOfReviewMode"), object: nil)
}
#IBAction func goNextTapped(_ sender: UIButton) {
let fullscreenVC = storyboard?.instantiateViewController(withIdentifier: "FullscreenPhoto") as! FullscreenPhotoVC
self.present(fullscreenVC, animated: true, completion: nil)
}
}
Here is my code for second UIViewController:
class FullscreenPhotoVC: UIViewController {
#IBOutlet weak var customLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(hideCustomLabel),
name: Notification.Name("NotificationOfReviewMode"),
object: nil)
}
#IBAction func goBackTapped(_ sender: UIButton) {
let reviewPhotosVC = storyboard?.instantiateViewController(withIdentifier: "ReviewPhotos") as! ReviewPhotosVC
self.present(reviewPhotosVC, animated: true, completion: nil)
}
#objc func hideCustomLabel(){
customLabel.isHidden = true
customLabel.textColor = .red
print("My func was executed.")
}
}
Here is my Storyboard:
Thanks if you read this.
The problem is that you are posting the notification before the next controller is initialised and has started observing. Also, there is no need for the notification you can do it directly. In this case I have used an extra variable shouldHideLabel as you cannot call the function hideCustomLabel() directly because this will lead to crash as the outlets are only initialised after view is loaded.
class ReviewPhotosVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//NotificationCenter.default.post(name: Notification.Name("NotificationOfReviewMode"), object: nil)
}
#IBAction func goNextTapped(_ sender: UIButton) {
let fullscreenVC = storyboard?.instantiateViewController(withIdentifier: "FullscreenPhoto") as! FullscreenPhotoVC
fullscreenVC.shouldHideLabel = true
self.present(fullscreenVC, animated: true, completion: nil)
}
}
class FullscreenPhotoVC: UIViewController {
var shouldHideLabel = false
#IBOutlet weak var customLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if shouldHideLabel {
hideCustomLabel()
}
/*
NotificationCenter.default.addObserver(self,
selector: #selector(hideCustomLabel),
name: Notification.Name("NotificationOfReviewMode"),
object: nil)
*/
}
#IBAction func goBackTapped(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
#objc func hideCustomLabel() {
customLabel.isHidden = true
customLabel.textColor = .red
print("My func was executed.")
}
}
Related
I have my main screen with only one button on it "Show next screen". When the second screen(VC) pops up it has 2 buttons (go back and toSelect button).
My goal is to when I show my second screen and select a button on it then go back to first. The button on my second screen will stay selected. How can I do that?
So basically I need to save my actions on the second screen so if I go back to it it will show everything I did.
What is the best way to do it?
Storyboard
The easiest way to achieve this using Delegate and protocol.
you should listen and save the changes of SecondViewController at FirstViewController using delegate methods.
And when you are presenting the secondViewController you will share the saved changes to secondViewController so that button can be selected on behalf of that information
Code -
class FirstViewController: UIViewController {
//secondViewController States
private var isButtonSelected = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func gotoSecondVCAction(_ sender: Any) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
guard let secondVC = storyBoard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
secondVC.isButtonSelected = isButtonSelected
secondVC.delegate = self
self.present(secondVC, animated: true, completion: nil)
}
}
extension FirstViewController: ButtonSelectionProtocol {
func button(isSelected: Bool) {
isButtonSelected = isSelected
}
}
and for secondViewController
protocol ButtonSelectionProtocol {
func button(isSelected:Bool)
}
class SecondViewController: UIViewController {
var isButtonSelected : Bool = false
var delegate:ButtonSelectionProtocol?
#IBOutlet weak var selectButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
if isButtonSelected {
selectButton.tintColor = .red
selectButton.setTitle("Selected", for: .normal)
}else{
selectButton.tintColor = .green
selectButton.setTitle("Select Me", for: .normal)
}
}
#IBAction func gobackAction(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
#IBAction func selectAction(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
isButtonSelected.toggle()
delegate?.button(isSelected: isButtonSelected)
}
}
Hai guys I am new to iOS development and still learning
I have a three View controllers ,viewController, SViewController, TViewController
in SviewController I have notification sender.post method on clicking a button
in viewController, TViewController viewdidload() methods I have .addobserver() methods
when I click a button on SViewController and post the notification
View Controller's selector is executed and not the TviewController
I have loaded the TviewController in FirstVC i.e ViewController viewdidload() method only
since segue performs again a viewdidload() from SviewController to TviewController with another button ,I tried to allocate a completion handler string to global variable so that it value remains same and print it(changed value) when view is loaded again, then I came to know that the completion handler is not at all executing here is the code
ViewController
import UIKit
extension Notification.Name{
static var fnote = Notification.Name("fnote")
}
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(fhandle(notification:)), name: .fnote, object: nil)
let storyboad = UIStoryboard(name: "Main", bundle: nil)
let tvc = storyboad.instantiateViewController(identifier: "1") as! TViewController
let _ = tvc.view
print(tvc.isViewLoaded)
print("journey")
}
#objc func fhandle(notification:Notification){
label.text = "Haii welcome to the hyderabad"
}
}
SViewController
import UIKit
var temp:String = "HHH"
class SViewController: UIViewController {
#IBAction func click(_ sender: Any) {
NotificationCenter.default.post(name: .fnote, object: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
TviewController
import UIKit
class TViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(handler(notification:)) , name: .fnote, object: nil)
print(temp)
print("happy")
}
#objc func handler(notification:Notification)
{
print("jackson")
label2.text = "Hurray"
temp = label2.text ?? "Nothing"
}
}
Can Some one please help me with this
You haven't shown or explained where / when / how you're going to display the view from TViewController, but to explain the first thing you're doing wrong...
In this code:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(fhandle(notification:)), name: .fnote, object: nil)
let storyboad = UIStoryboard(name: "Main", bundle: nil)
let tvc = storyboad.instantiateViewController(identifier: "1") as! TViewController
let _ = tvc.view
print(tvc.isViewLoaded)
print("journey")
}
as soon as viewDidLoad() finishes (that is, immediately after the print("journey") line), your tvc instance of TViewController is destroyed. That is, it's dumped from memory and no code inside it can execute, because it no longer exists.
That's called "going out of scope."
So, when you try to post a notification from SViewController, there is no TViewController in existence so its code is not running to "observe" the notification.
Here is a very simple example of multiple view controllers observing a notification:
When you push from ViewController to TViewController, you get a new instance of TViewController and the original instance of ViewController still exists.
When you push from TViewController to SViewController, you get a new instance of SViewController and both ViewController and TViewController still exist.
When you tap the "Post" button, ViewController and TViewController will each execute the func you assigned to observe the notification.
When you then go Back to TViewController, you'll see its label text has changed, and when you go back to ViewController you'll see its label text has also changed.
Note that if you then push to TViewController again, you get a new instance and its label will be back at its original text.
extension Notification.Name{
static var fnote = Notification.Name("fnote")
}
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(fhandle(notification:)), name: .fnote, object: nil)
print("journey")
}
#objc func fhandle(notification:Notification) {
print("Got it!")
label.text = "Haii welcome to the hyderabad"
}
}
class TViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(handler(notification:)), name: .fnote, object: nil)
print("happy")
}
#objc func handler(notification:Notification) {
print("jackson")
label2.text = "Hurray"
}
}
class SViewController: UIViewController {
#IBOutlet var infoLabel: UILabel!
#IBAction func click(_ sender: Any) {
NotificationCenter.default.post(name: .fnote, object: nil)
infoLabel.text = "Notification Posted"
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
let storyboad = UIStoryboard(name: "Main", bundle: nil)
let tvc = storyboad.instantiateViewController(identifier: "1") as! TViewController
let _ = tvc.view
print(tvc.isViewLoaded)
This is not the way to test the notification. Push your "TViewController" controller from the "ViewController" controller. Then post notification from your target controller.
I've created UIAlertView that has 2 buttons positive button and negative button.
AlertView is viewcontroller as well.
I am opening AlertVC from Main viewController.
Here is my AlertVC
class AlertVC: UIViewController {
var transitioner : CAVTransitioner
#IBOutlet weak var alertPositiveBtn: IFOButton!
#IBOutlet weak var alertNegativeBtn: IFOButton!
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
self.transitioner = CAVTransitioner()
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.modalPresentationStyle = .custom
self.transitioningDelegate = self.transitioner
}
convenience init() {
self.init(nibName:nil, bundle:nil)
}
required init?(coder: NSCoder) {
fatalError("NSCoding not supported")
}
#IBAction func postiveBtnPressed(_ sender: IFOButton) {
}
#IBAction func negativeBtnPressed(_ sender: IFOButton) {
}
#IBAction func closeBtnPressed(_ sender: UIButton) {
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
}
What I want: I want MainViewController somehow detect which button pressed negative or positive.
Is anyone can tell me how could I do this?
UPDATE: after using delegate pattern
#IBAction func positiveBtnPressed(_ sender: IFOButton) {
delegate?.positiveBtnPressed(onAlertVC: self)
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
#IBAction func negativeBtnPressed(_ sender: IFOButton) {
delegate?.negativeBtnPressed(onAlertVC: self)
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
Here is what I did on MainViewController
class MainViewController: UIViewController, AlertVCDelegate
and here is my functions
func positiveBtnPressed(onAlertVC: IFOAlertVC) {
print("Pos")
}
func negativeBtnPressed(onAlertVC: IFOAlertVC) {
print("Neg")}
It still not being called.
This is a textbook example of the delegate pattern.
Add a protocol to your AlertVC:
protocol AlertVCDelegate : class {
func positiveBtnPressed(onAlertVC: AlertVC)
func negativeBtnPressed(onAlertVC: AlertVC)
}
Then create a weak property in your AlertVC class and pass the button presses to it:
class AlertVC : UIViewController {
weak var delegate: AlertVCDelegate?
...
#IBAction func postiveBtnPressed(_ sender: IFOButton) {
delegate?.positiveBtnPressed(onAlertVC: self)
}
#IBAction func negativeBtnPressed(_ sender: IFOButton) {
delegate?.negativeBtnPressed(onAlertVC: self)
}
}
Implement the AlertVCDelegate protocol in your MainViewController, and set the delegate when you present the AlertVC from your MainViewController.
If you present the alert vc from a segue, use the prepare(for: sender:) method to set the MainViewController as the delegate.
You have to CTRL + Drag the button from your app Storyboard to their own method.
Your IFOButton should be an UIButton derived class (inheritance).
Even you only need one method to do that
#IBAction internal func handleButtonTap(_ sendder: UIButton) -> Void
{
if sender === alertPositiveBtn
{
// Do something positive here
}
else
{
// Do something negative here
}
}
I want to change label from another viewController.
First viewcontroller is MenuController. Second one is LoginViewController.
I want to change MenuController's Label.text from LoginViewController.
In LoginViewController:
let viewController = MenuController()
viewController.changeLabel("logout")
In MenuController:
class MenuController: UITableViewController {
var attractionImages = [String]()
var attractionNames = [String]()
var webAddresses = [String]()
#IBOutlet weak var loginLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
loginLabel.text = "Login"
print(loginLabel.text)
}
func changeLabel(Log: String)O {
self.loginLabel.text = log
print (log)
}
But an error occur.
fatal error: unexpectedly found nil while unwrapping an Optional value
How can I solve it?
Thanks for your help.
Another way to achieve that is you can use NSNotificationCenter. Blow is the example for that:
In your MenuController add this code:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshLbl:", name: "refresh", object: nil)
}
Also add this helper method:
func refreshLbl(notification: NSNotification) {
print("Received Notification")
lbl.text = "LogOut"
}
Now in your LoginViewController your back button action will look like:
#IBAction func back(sender: AnyObject) {
NSNotificationCenter.defaultCenter().postNotificationName("refresh", object: nil, userInfo: nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
Now when ever you press back button from LoginViewController your refreshLbl method will call from MenuController.
For more info refer THIS example.
Swift 3 version:
In your MenuController (where the label needs to be changed) add this code:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(refreshLbl),
name: NSNotification.Name(rawValue: "refresh"),
object: nil)
}
Also add this helper method:
#objc func refreshLbl() {
print("Received Notification")
lbl.text = "LogOut"
}
Now in your LoginViewController your back button action will look like:
#IBAction func backButton(_ sender: Any) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "refresh"), object: nil)
// Any additional code...
}
Now when ever you press back button from LoginViewController your refreshLbl() method will call from MenuController.
I am having troubles showing the Game Center leaderboard when the user presses a button on my SecondViewController (game over screen with score/top score). The UIbutton is ShowLeaderboard which you'll see below.
The error I get is:
Warning: Attempt to present <GKGameCenterViewController: 0x7fb1c88044a0> on <UIViewController: 0x7fb1c2624e90> whose view is not in the window hierarchy!
I have tried dismissing the view first but no matter what I do I can't just get the leaderboard view to show. Below is my SecondViewController code:
import UIKit
import GameKit
class SecondViewController: UIViewController, GKGameCenterControllerDelegate {
#IBOutlet var scoreLabel: UILabel!
#IBOutlet var HighScoreLabel: UILabel!
var receivedString: String = ""
var receivedHighScore: String = ""
override func viewDidLoad() {
super.viewDidLoad()
scoreLabel.text = receivedString
HighScoreLabel.text = receivedHighScore
}
#IBAction func ShowLeaderboard(sender: UIButton) {
dismissViewControllerAnimated(true, completion:nil)
showLeader()
}
func showLeader() {
var vc = self.view?.window?.rootViewController
var gc = GKGameCenterViewController()
gc.gameCenterDelegate = self
vc?.presentViewController(gc, animated: true, completion: nil)
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!)
{
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func retryButton(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
EDIT Got it working! All I had to do was change
var vc = self.view?.window?.rootViewController
to
var vc = self
You are probably seeing this warning because you are displaying the Leaderboard before dismissViewControllerAnimated has finished the animation. You should place the showLeader() inside the completion argument of dismissViewControllerAnimated.
Here is my Code. Hope it helps!
if (self.levelGameAttemptCount == 3)
{
self.canRestart = false
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewControllerWithIdentifier("gameOverControllerID") as! GameOverController
self.view!.window!.rootViewController!.dismissViewControllerAnimated(false, completion: nil)
UIApplication.sharedApplication().keyWindow!.rootViewController!.presentViewController(vc, animated: true, completion: nil)
}
else
{
self.canRestart = true
}