How to customize Firebase Authentication UI in Swift - swift

I am building an authentication system using the Firebase prebuilt UI, and I want to customize the UI to fit the program. Say I want to set the background color to black and change the corner radius of the buttons, is there any way I can do this? I've tried sub-classing the authPickerViewController but somehow it didn't work. I did some searching, but couldn't find any tutorial or recent problems related to this either.
Here's what I have in my MainViewController
class LoginViewController: UIViewController, FUIAuthDelegate{
override func viewDidLoad() {
super.viewDidLoad()
let authUI = FUIAuth.defaultAuthUI()
authUI?.delegate = self
let providers: [FUIAuthProvider] = [
FUIEmailAuth(),
FUIGoogleAuth(),
FUIPhoneAuth(authUI:FUIAuth.defaultAuthUI()!)]
authUI?.providers = providers
let authViewController = authUI?.authViewController()
authViewController!.modalPresentationStyle = .fullScreen
authViewController!.setNavigationBarHidden(true, animated: false)
self.present(authViewController!, animated: false, completion: nil)
}
func application(_ app: UIApplication, open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
let sourceApplication = options[UIApplicationOpenURLOptionsKey.sourceApplication]
if FUIAuth.defaultAuthUI()?.handleOpen(url, sourceApplication: sourceApplication as? String) ?? false {
return true
}
return false
}
}
And here is the subclass I created:
class FUICustomAuthPickerViewController: FUIAuthPickerViewController,FUIAuthDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .black
}
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
return FUICustomAuthPickerViewController(nibName: "FUICustomAuthPickerViewController",
bundle: Bundle.main,
authUI: authUI)
}
}
On the Firebase documentation for customization, they say that:
You can customize the sign-in screens by subclassing FirebaseUI's view controllers and specifying them in FUIAuth's delegate methods.
I am a beginner, how can I do that?
Edited:
So by following the instructions on this link I managed to add stuff to the pre-built UI by creating an extension to the FUIAuthDelegate.
extension LoginViewController:FUIAuthDelegate {
func application(_ app: UIApplication, open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
let sourceApplication = options[UIApplicationOpenURLOptionsKey.sourceApplication]
if FUIAuth.defaultAuthUI()?.handleOpen(url, sourceApplication: sourceApplication as? String) ?? false {
return true
}
return false
}
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
let vc = FUIAuthPickerViewController(authUI: authUI)
let view = UIView(frame: .zero)
view.backgroundColor = .black
view.translatesAutoresizingMaskIntoConstraints = false
vc.view.addSubview(view)
NSLayoutConstraint.activate([
view.heightAnchor.constraint(equalTo: vc.view.heightAnchor, multiplier: 1),
view.widthAnchor.constraint(equalTo: vc.view.widthAnchor, multiplier: 1)])
return vc
}
}
Turns out subclass is not necessarily needed. However, I can't seem to make this view I created to be the background, it either covers everything or nothing at all.I tried changing the background color of the view directly, didn't work. Anyone knows how to do this?

Solved the problem using the method and one of the comments provided in this link. Turns out, apart from subclassing, you have to add the following two methods to your subclass for it to work.
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, authUI: FUIAuth?) {
super.init(nibName: nil, bundle: Bundle.main, authUI: authUI!)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
However, the approach I used (not sure if other approaches raise the same problem) also caused another problem - the email sign-in button stopped responding - which is addressed in this link by presenting the view controller with a navigationViewController because the email sign-in button works together with the navigation bar, you can get rid of it once you have presented the view with a navigationViewController.
Now the complete subclass looks like this:
import UIKit
import FirebaseUI
class FUICustomAuthPickerViewController: FUIAuthPickerViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, authUI: FUIAuth?) {
super.init(nibName: nil, bundle: Bundle.main, authUI: authUI!)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
imageViewBackground.backgroundColor = .eatstrOrange
view.insertSubview(imageViewBackground, at: 0)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
}
}
And here's the main view controller:
import UIKit
import FirebaseUI
class LoginViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let authUI = FUIAuth.defaultAuthUI()
let delegate = authUI?.delegate
authUI?.delegate = delegate
let providers: [FUIAuthProvider] = [
FUIGoogleAuth(),
FUIEmailAuth(),
FUIPhoneAuth(authUI:FUIAuth.defaultAuthUI()!)]
authUI?.providers = providers
let authViewController = FUICustomAuthPickerViewController(authUI: authUI!)
authViewController.modalPresentationStyle = .fullScreen
navigationController?.pushViewController(authViewController, animated: false)
}
}
Final outcome

Related

Swift, Get action Bar Button Action from Container

I have ParentViewController.swift and ChildContainer.swift.
In ParentViewController, I have bar button item action like below :
#IBAction func onClickBarItemRefresh(_ sender: UIBarButtonItem) {
print("Refresh")
}
I want to know, how to call/get this action from ChildContainer?
I can change the title with parent?.navigationItem.title = "YourName", but I cannot find related question about to get the action.
Addition Info:
I have like 4 or 5 container in 1 ParentViewController, so all logic is on their container. So I need call the action on 4 or 5 child container with different login inside the action
First declare a callback function in the ContainerViewController.
var refreshButtonTapped: (() -> Void)?
In ParentViewController where you initialise ContainerViewController give action of the callback function.
In your case create a separate method i.e. refreshContent() and call it from onClickBarItemRefresh() method and also in refreshButtonTapped function where you initialise ContainerViewController.
class ParentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(onClickBarItemRefresh(_:)))
showContainerVC()
}
#objc func onClickBarItemRefresh(_ sender: UIBarButtonItem) {
refreshContent()
}
private func refreshContent() {
print("Refresh Content")
}
func showContainerVC() {
let vc = ContainerViewController()
// call refreshContent() inside the callback function
vc.refreshButtonTapped = { [weak self] in
self?.refreshContent()
}
let nav = UINavigationController(rootViewController: vc)
self.addChild(nav)
nav.view.frame = CGRect(x: 20, y: 100, width: 320, height: 200)
self.view.addSubview(nav.view)
nav.didMove(toParent: self)
}
}
In ContainerViewController where you want to perform the action of refreshContent() just call the callback function refreshButtonTapped like below. For example i call it from viewDidAppear() method. It will perform the action of refreshing in ParentViewController.
class ContainerViewController: UIViewController {
var refreshButtonTapped: (() -> Void)?
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(#function)
// call this where you want to perform refreshing of ParentViewController
refreshButtonTapped?()
}
}

How to know when VC2 was dismissed on VC1?

I have ViewController1 that goes to ViewModel and then to Coordinator to present ViewController2.
The problem is: I need to know when VC2 was dismissed on VC1.
What I need to do: When VC2 is dismissed, I need to reload my table from VC1.
I can not use Delegate since I cant communicate between then (because of Coordinator).
Any help please?
Adding some code: My Coordinator:
public class Coordinator: CoordinatorProtocol {
public func openVC1() {
let viewModel = ViewModel1(coordinator: self)
guard let VC1 = ViewControllerOne.instantiate(storyboard: storyboard, viewModel: viewModel) else {
return
}
navigationController?.pushViewController(VC1, animated: true)
}
public func openVC2() {
let viewModel = ViewModel2()
guard let alertPriceDeleteVC = ViewControllerTwo.instantiate(storyboard: storyboard, viewModel: viewModel) else {
return
}
let nav = UINavigationController(rootViewController: VC2)
navigationController?.present(nav, animated: true, completion: nil)
}
CoordinatorProtocol:
public protocol CoordinatorProtocol {
func openVC1()
func openVC2()
}
My ViewModel1 calling VC2 through coordinatorDelegate:
func openVC2() {
coordinator.openVC2()
}
What I do when I finish ViewController2 and send user back do VC1:
navigationController?.dismiss(animated: true, completion: nil)
You need to to assign delegate value from prepare. Or you can assign delegate with initialize RedScreenVC(self) from your ViewController if u don't want to use storyboard/xib.
import UIKit
class ViewController: UIViewController, NavDelegate {
func navigate(text: String, isShown: Bool) {
print("text: \(text) isShown: \(isShown)")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "RedScreenVC") {
let redScreenVC = segue.destination as? RedScreenVC
redScreenVC?.delegate = self
}
}
#IBAction func nextPageButtonEventLustener(_ sender: Any) {
performSegue(withIdentifier: "RedScreenVC", sender: sender)
}
}
import UIKit
protocol NavDelegate {
func navigate(text: String, isShown: Bool)
}
class RedScreenVC: UIViewController {
weak var delegate: NavDelegate?
var redView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
var navigateButton: UIButton = {
let button = UIButton(frame: CGRect(x: 200, y: 350, width: 150, height: 50))
button.setTitle("Navigate", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.backgroundColor = .blue
return button
}()
#objc func buttonAction(){
if self.redView.backgroundColor == .gray {
self.redView.backgroundColor = .systemPink
}
self.delegate.navigate(text:"", isShown: true)
}
override func viewDidLoad() {
navigateButton.layer.cornerRadius = 25
redView.backgroundColor = UIColor.gray
delegate.navigate(text: "Navigation Success", isShown: true)
view.addSubview(redView)
view.addSubview(navigateButton)
}
}
If you do not want to use storyboard.
let redScreenVC = RedScreenVC()
redScreenVC.delegate = self
class RedScreenVC: UIViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init() {
super.init(nibName: nil, bundle: nil)
self.initialize()
}
func initialize() {
self.view.backgroundColor=CustomColor.PAGE_BACKGROUND_COLOR_1
//From here you need to create your email and password textfield
}
}

Storyboard doesn't contain a view controller with identifier error?

I'm trying to present a programmatically made viewcontroller on a viewcontroller, where I can't figure out how to make ID of such made-up viewcontroller.
As can be seen in the code under, I have a base view controller, 'ViewController' and if I click a button(didTapButton) I want a programmatically made view controller(SecondViewController) show up.
Though I can't set the second view controller's name, that I can't even execute the code -- instantiateViewController(withIdentifier: "SecondController").
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func didTapButton(_ sender: Any) {
let controller = storyboard!.instantiateViewController(withIdentifier: "SecondController")
present(controller, animated: true)
}
}
......
class SecondViewController: UIViewController {
private var customTransitioningDelegate = TransitioningDelegate()
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: "SecondController", bundle: nibBundleOrNil)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
}
How can I set up the second view controller's ID? If it's not what should be done, what else can I try?
instantiateViewController lets you instanciate something that is defined in a given storyboard. So either you name it in the storyboard via xcode or you must do something else. For example, instanciate the object from code, ie let c=SecondViewController() (with appropriate parameters). You are trying to mix different ways to instanciate an object.
You don't need any identifiers for programmatically created vcs just do
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .green
}
}
Use like
#IBAction func didTapButton(_ sender: Any) {
let vc = SecondViewController()
present(vc, animated: true)
}
Edit:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .green
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let vc2 = SecondViewController()
vc2.providesPresentationContextTransitionStyle = true
vc2.definesPresentationContext = true
vc2.modalPresentationStyle = .overCurrentContext
self.present(vc2, animated: true)
}
}
}
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = UIView()
v.backgroundColor = .red
view.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
v.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
v.widthAnchor.constraint(equalToConstant:200),
v.heightAnchor.constraint(equalToConstant:200)
])
}
}

'UIStoryboard.type' does not have member named 'centerViewController'

I am following THIS tutorial but I didn't downloaded starter project file from that tutorial because I want to make it differently but I am stuck here since an hour because I got this Error:
'UIStoryboard.type' does not have member named 'centerViewController'
Here I am trying to add subView (CenterViewController) into ContainerViewController.
Here is my code for ContainerViewController.swift
import UIKit
import QuartzCore
class ContainerViewController: UIViewController, CenterViewControllerDelegate {
var centerNavigationController: UINavigationController!
var centerViewController: CenterViewController!
override init() {
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
centerViewController = UIStoryboard.centerViewController()
//'UIStoryboard.type' does not have member named 'centerViewController'
centerViewController.delegate = self
// wrap the centerViewController in a navigation controller, so we can push views to it
// and display bar button items in the navigation bar
centerNavigationController = UINavigationController(rootViewController: centerViewController)
view.addSubview(centerNavigationController.view)
addChildViewController(centerNavigationController)
centerNavigationController.didMoveToParentViewController(self)
}
}
This is my CenterViewController.swift
import UIKit
#objc
protocol CenterViewControllerDelegate {
optional func toggleLeftPanel()
optional func collapseSidePanels()
}
class CenterViewController: UIViewController {
var delegate: CenterViewControllerDelegate?
#IBAction func tableTapped(sender: AnyObject) {
}
}
And this is my AppDelegate.swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let containerViewController = ContainerViewController()
window!.rootViewController = containerViewController
window!.makeKeyAndVisible()
return true
}
Can anyOne give me any Idea what I am missing?
You are missing the extension the tutorial author has provided that makes the UIStoryboard.centerViewController() method exist. The code is at the bottom of ContainerViewController.swift in his downloadable starter project, and I've copied it down below as well:
private extension UIStoryboard {
class func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) }
class func leftViewController() -> SidePanelViewController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("LeftViewController") as? SidePanelViewController
}
class func rightViewController() -> SidePanelViewController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("RightViewController") as? SidePanelViewController
}
class func centerViewController() -> CenterViewController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("CenterViewController") as? CenterViewController
}
}
Add this to the bottom of your ContainerViewController.swift and it should work. (That is, as long as you have these view controllers set up in the right storyboard files with the right identifiers.)

Swift/IOS8 error: "fatal error: Can't unwrap Optional.None"

I know there are already a couple issues on this, but I can't figure it out. The previous solved issues would suggest that 'profileViewController' is nil, but I don't know why that would be the case. The UI is completely programmatic, no IB. Getting:
"fatal error: Can't unwrap Optional.None"
on pushViewController() in the following code:
class FavoritesViewController: UIViewController {
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
// Custom initialization
self.title = "Favorites"
self.tabBarItem.image = UIImage(named: "MikeIcon")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.backgroundColor = UIColor.redColor()
let profileButton = UIButton.buttonWithType(.System) as UIButton
profileButton.frame = CGRectMake(60, 300, 200, 44)
profileButton.setTitle("View Profile", forState: UIControlState.Normal)
profileButton.addTarget(self, action: "showProfile:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(profileButton)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func showProfile(sender: UIButton) {
let profileViewController = ProfileViewController(nibName: nil, bundle: nil)
self.navigationController.pushViewController(profileViewController, animated: true)
}
Here's the relevant portion of AppDelegate.swift:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
// Override point for customization after application launch.
let feedViewController = FeedViewController(nibName: nil, bundle: nil)
let favoritesViewController = FavoritesViewController(nibName: nil, bundle: nil)
let profileViewController = ProfileViewController(nibName: nil, bundle: nil)
let tabBarController = UITabBarController()
self.window!.rootViewController = tabBarController
tabBarController.viewControllers = [feedViewController, favoritesViewController, profileViewController]
self.window!.backgroundColor = UIColor.whiteColor()
self.window!.makeKeyAndVisible()
return true
}
Is the Navigation Controller object self.navigationController nil?
The variable type on the navigationController property is an unwrapped optional. If your View Controller is not inside a UINavigationController that would be the problem.
To prevent the crash code along the following lines should be written:
if (self.navigationController)
{
self.navigationController.pushViewController(profileViewController, animated: true)
}
Alternatively you could write the code as:
self.navigationController?.pushViewController(profileViewController, animated: true)
The ? operator will prevent any further code from being executed, if self.navigationController evaluates to nil.