I am implementing a Terms & Conditions view into my app and the user has to accept them to proceed, then when they do accept them, they no longer have to go through the Terms & Conditions view. I followed a tutorial on how to integrate UserDefaults and store the value locally if someone does accept the terms. However I am stuck with implementing it into the root view controller. Specifically stuck on my viewDidAppear function. What goes in the if and else statements?
class TermsAndConditionsViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var termsTextView: UITextView! {
didSet {
termsTextView.delegate = self
}
}
#IBOutlet weak var acceptButton: UIButton! {
didSet {
acceptButton.isHidden = true
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
acceptButton.isHidden = scrollView.contentOffset.y + scrollView.bounds.height < scrollView.contentSize.height
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if UserDefaults.standard.bool(forKey: "termsAccepted") {
} else {
}
}
#IBAction func acceptButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "toPeekView", sender: sender)
}
}
Probably, you mean "Show a ViewController on First Launch Only".
Using UserDefaults for this purpose is a good idea, however, checking if the term accepted should not be at the TermsAndConditionsViewController layer, instead, it should be in AppDelegate - application:didFinishLaunchingWithOptions, you can decide in it whether the root ViewController should be the TermsAndConditionsViewController or the other ViewController (HomeViewController for example).
AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootViewController = storyboard.instantiateViewController(withIdentifier: UserDefaults.standard.bool(forKey: "termsAccepted") ? "termsViewControllerID" : "homeViewControllerID")
window?.rootViewController = rootViewController
return true
}
TermsAndConditionsViewController:
class TermsAndConditionsViewController: UIViewController {
//...
#IBAction func acceptButtonTapped(_ sender: Any) {
UserDefaults.standard.set(true, forKey: "termsAccepted")
performSegue(withIdentifier: "toPeekView", sender: sender)
}
//...
}
Hope this helped.
Related
i want to pass the container from my Tabbar to my VC, I tried to do it something like this, but does not work at all. How do I fix it?
My VC
class FavVC: UIViewController {
var container: NSPersistentContainer!
override func viewDidLoad() {
super.viewDidLoad()
guard container != nil else { fatalError("This view needs a persistent container.") }
NSLayoutConstraint.activate([
])
}
}
My Tabbar
class TabBar:UITabBarController {
var container: NSPersistentContainer!
#IBOutlet weak var TabBarContent: UITabBar!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let favVC = segue.destination as? FavVC {
favVC.container = container
}
}
override func viewDidLoad() {
super.viewDidLoad()
guard container != nil else { fatalError("This view needs a persistent container.") }
print("it works!")
}
}
I think you don't need to use segue here, because UITabBarController do navigation automatically. Try to implement UITabBarControllerDelegate, add this extension for your TabBar class:
extension TabBar: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if let favVC = viewController as? FavVC {
favVC.container = container
}
return true
}
}
Then modify TabBar viewDidLoad like this:
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
guard container != nil else { fatalError("This view needs a persistent container.") }
print("it works!")
}
Now you have container before FavVC viewDidLoad call.
If any of you have the same struggle as I do.
You can simply add
if let tabBarVC = self.tabBarController as? TabBar {
self.container = tabBarVC.container
}
on your UIViewController
I am trying to create a framework (Login VC) which contains a view controller. I have successfully imported the framework and presented the VC, but the view is not showing. I have a print function in the imported viewDidLoad and it is printing. What am I missing?
Framework VC:
public class LoginVC: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
}
}
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func viewDidLoad() {
super.viewDidLoad()
print("View Loaded") // Firing
}
#objc func buttonPressed() {
print("hello")
}
}
Framework VC Xib:
This is view debugger when I present the framework VC
-- Update: This is how I am showing the VC ---
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = LoginVC()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
-- Update --
Since many comments relate to the app delegate, I first present a general ViewController which then will present my login framework VC.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = ViewController()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(presentNext), for: .touchUpInside)
}
}
#objc func presentNext() {
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
Now, when I present the login framework, all I get is a black screen.
-- Update --
I can change the background color of the view in viewdidLoad, but the xib views are not shown. Why is this?..
Frameworks with xibs connected to viewController require explicit loading from the bundle.
Usually when we create a cocoaTouch UIViewController with xib, this is handled for us. However, when using frameworks, it is not handled.
To solve this, I add load the xib in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Frameworks require loading of the xib to connect xib to view controller
let bundle = Bundle(for: type(of: self))
bundle.loadNibNamed("viewControllerName", owner: self, options: nil)
}
You need to call your presentNext() after the ViewController has actually appeared -- not in viewDidLoad, and not even in viewWillAppear.
Like this:
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentNext()
}
#objc func presentNext() {
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
Here's the working test project:
https://github.com/drewster99/SO_LoginVC
Also, maybe double-check that your ViewController.xib has the IBOutlet actually attached for the button. It's actually got to be that. Everything else looks good.
Here's what I've got:
AppDelegate:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = ViewController()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
}
ViewController.swift:
import UIKit
import LoginFrameworkThing
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(presentNext), for: .touchUpInside)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
#objc func presentNext() {
print("Presenting next...")
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
The button is connected in ViewController, and LoginVC (both .xib and .swift) exist in the framework.
I updated the sample project. Check the link.
I have function call (pop view) in my 1st view controller which have to be called only once in app. Since then whenever I return back to 1st View controller the function need not to be called again.
func popView() {
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "popView") as! popView
self.addChild(popOverVC)
popOverVC.view.frame = self.view.frame
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParent: self)
}
I have tried the following code and previous other sources in stack overflow, didn't work though..
///// Once Action in View Controller
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if self.isBeingPresented || self.isMovingToParent {
// Perform an action that will only be done once
popView()
}
}
Maybe this works. In your ViewController, add a static property:
static var shouldPop = true
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if isBeingPresented || isMovingToParent {
// Perform an action that will only be done once
if (type(of: self).shouldPop) {
type(of: self).shouldPop = false
popView()
}
}
}
Of course, depending on your setup, this won't work if you have more than one instance of this viewcontroller that should keep their own state on whether popView should be called or not.
You should call this in viewDidLoad method. It's called once per UIViewController life cycle.
Documentation here.
Just like this:
override func viewDidLoad() {
super.viewDidLoad()
if self.isBeingPresented || self.isMovingToParent {
// Perform an action that will only be done once
popView()
}
}
If your way is pop view after you was once in view controller you could do like this:
/// bool that help indicate your visit
var isViewControllerVisited = false
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if isViewControllerVisited {
// Perform an action that will only be done once
popView()
}
//change it here
isViewControllerVisited = true
}
Hope it's help!
If you want to call that PopView function only once in you App then try this,
In App delegate, set bool value
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UserDefaults.standard.set(true, forKey: "showPop") // like so
return true
}
Then, in first view controller try this,
func hasLaunchPop() {
let isshowPop: Bool = UserDefaults.standard.bool(forKey: "showPop")
if isshowPop == true {
popView()
UserDefaults.standard.set(false, forKey: "showPop")
}
}
then in viewdidload call like this,
override func viewDidLoad() {
super.viewDidLoad()
hasLaunchPop()
}
So that PopView appears only once in your app when its launched and will never show up again.
For me I prefer to use lazy loading. This allow not to write any logic, just need to use Swift lazy var declaration. Something like this:
private lazy var viewDidAppearOnce: Bool = {
popView()
}()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
_ = viewDidAppearOnce
}
Below code, try it..
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UserDefaults.standard.set(false, forKey: "isPopOverVCPopped")
if UserDefaults.standard.bool(forKey: "isPopOverVCPopped") == false {
UserDefaults.standard.set(true, forKey: "isPopOverVCPopped")
popView()
}
}
As per the view controller's lifecycle, the viewDidLoad method only gets called once. so you should call your method only there.
Or you can try the following code:
var isScreenAppeared = false
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !isScreenAppeared {
popView()
}
isScreenAppeared = true
}
I've 2 ViewController's: VC1 and VC2. In VC1 i've :
#IBAction func cliclOnBtn(_ sender: UIButton) {
CameraController.takePicture()
}
...
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
print("didFinishPickingMediaWithInfo", picker.sourceType)
let _image = (info[UIImagePickerControllerOriginalImage] as! UIImage)
ImageView.image = _image
Images.append(_image)
}
In VC2 i'm passing array of _image, but when i try to back on VC1, photos from camera adding by clicking again. How to reset data? How to make reset of an array of photo, when back button pressed ?
You can implement viewWillAppear in VC1, and then remove all elements of the image Array. This way, when you tap back on VC2, the VC1 will have it's image Array empty.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
Images.removeAll()
}
Btw, consider renaming your instance variables, lowercasing the initial character, it's a good practice.
Use protocol with delegate for sending data to back
You can update UI or code in delegate function
import UIKit
protocol DestinationViewControllerDelegate {
func updateData(text:String); // you can use Array, Dictonary, Modal as per your requirment
}
class DestinationViewController: UIViewController {
var delegate:FullCalendarViewDelegate! = nil
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func doneButton(sender: AnyObject) {
delegate?.updateData("Got new data")
self.navigationController?.popViewControllerAnimated(true)
}
}
// Sender View Controller
class SenderViewController: UIViewController, DestinationViewControllerDelegate {
#IBOutlet weak var titleLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func NextButton(sender: AnyObject) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let dash:DestinationViewControllerDelegate = storyBoard.instantiateViewControllerWithIdentifier("DestinationViewControllerDelegate") as! DestinationViewControllerDelegate
dash.delegate = self
self.navigationController?.pushViewController(dash, animated: true)
}
func updateData(text:String) {
titleLabel.text = text
}
}
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
}
}