viewWillAppear/DidAppear called twice when changing rootViewController with an Animation - swift

Here is the code of my animation/transition :
if let window = self.window where animated {
window.rootViewController?.dismissViewControllerAnimated(true, completion: {
UIView.transitionFromView(window.rootViewController!.view, toView: tabBarController.view, duration: 0.8, options: .TransitionFlipFromLeft, completion: { _ in
window.rootViewController = tabBarController
})
})
}
Without the animation code, (just setting the rootViewControlller), the problem does not appear.
Any idea why it makes the view appear twice and how I could fix this ?

using UIViewController.transitionFromViewController1 ,instead of your UIView.transitionFromView.act like it :
//
// AppDelegate.swift
// helloapp
//
// Created by quota on 2/18/16.
// Copyright © 2016 liu. All rights reserved.
//
import UIKit
class ViewController1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.greenColor()
let button = UIButton()
button.setTitle("transit", forState: .Normal)
button.frame = CGRectMake(20, 20, 100,20)
button.addTarget(self, action: "click:", forControlEvents: .TouchDown)
view.addSubview(button)
}
func click(sender:UIButton!){
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
if let window = appDelegate.window {
// let v2 = ViewController2()
if let r = window.rootViewController{
r.transitionFromViewController(
appDelegate.v1!
,toViewController:appDelegate.v2!,duration:0.8,options: .TransitionFlipFromLeft,animations:nil){_ in }
}
}
}
}
class ViewController2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blueColor()
}
override func viewWillAppear(animated: Bool) {
print(animated)
}
}
class ViewController3: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.redColor()
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var v1 : ViewController1?
var v2 : ViewController2?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window!.rootViewController = ViewController3()
v1 = ViewController1()
v2 = ViewController2()
v1!.view.frame = self.window!.frame
self.window!.rootViewController?.addChildViewController(v1!)
self.window!.rootViewController?.view.addSubview(v1!.view)
self.window!.rootViewController?.addChildViewController(v2!)
self.window?.makeKeyAndVisible()
return true
}
}

Related

Title Not Showing in Simulator and doesn't shift ViewController to next after button click

Title is not showing in Simulator
import UIKit
class WelcomeSpotifyViewController: UIViewController {
private let signInButton: UIButton = {
let button = UIButton()
button.backgroundColor = .white
button.setTitle("Sign In with Spotify", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
title = "Spotify"
view.backgroundColor = .systemGreen
view.addSubview(signInButton)
signInButton.addTarget(self, action: #selector(didTapSignIn), for: .touchUpInside)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
signInButton.frame = CGRect(x:20, y: view.height-50-view.safeAreaInsets.bottom, width: view.width-40, height: 50)
}
#objc func didTapSignIn() {
let vc = SpotifyAuthViewController()
vc.navigationItem.largeTitleDisplayMode = .never
navigationController?.pushViewController(vc, animated: false)
}
}
When I launch the simulator, the title does not show. And when I click the button "Sign In with Spotify," I do not transfer to the SpotifyAuthViewController.
Here is the code for the SpotifyAuthViewController
import UIKit
import WebKit
class SpotifyAuthViewController: UIViewController, WKNavigationDelegate {
private let webView: WKWebView = {
let prefs = WKWebpagePreferences()
prefs.allowsContentJavaScript = true
let config = WKWebViewConfiguration()
config.defaultWebpagePreferences = prefs
let webView = WKWebView(frame: .zero, configuration: config)
return webView
}()
override func viewDidLoad() {
super.viewDidLoad()
title = "Sign In"
view.backgroundColor = .systemBackground
webView.navigationDelegate = self
view.addSubview(webView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
webView.frame = view.bounds
}
}
After the user is registered, I want them to be directed to a standard login/register page where they create a username, register with email, and create a password.
import UIKit
class LoginViewController: UIViewController {
private let usernameEmailField: UITextField = {
return UITextField()
}()
private let passwordField: UITextField = {
let field = UITextField()
field.isSecureTextEntry = true
return field
}()
private let loginButton: UIButton = {
return UIButton()
}()
private let createAccountButton: UIButton = {
return UIButton()
}()
private let headerView: UIView = {
return UIView()
}()
override func viewDidLoad() {
super.viewDidLoad()
addSubviews()
view.backgroundColor = .systemBackground
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
handleNotAuthenticated()
//Check auth status
}
private func handleNotAuthenticated() {
if SpotifyAuthManager.shared.isSignedIn == false {
let loginVC = WelcomeSpotifyViewController()
loginVC.modalPresentationStyle = .overCurrentContext
present(loginVC, animated: false)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//assign frames
}
private func addSubviews() {
view.addSubview(usernameEmailField)
view.addSubview(createAccountButton)
view.addSubview(passwordField)
view.addSubview(loginButton)
view.addSubview(headerView)
}
#objc private func didTabLoginButton(){}
#objc private func didTapCreateAccountButton(){}
}
If the user is not connected with Spotify, then they are redirected to the WelcomeSpotifyViewController. Once they enter their username and pass, they are taken to the home page.
import UIKit
import FirebaseAuth
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
handleNotAuthenticated()
//Check auth status
}
private func handleNotAuthenticated() {
if Auth.auth().currentUser == nil {
let loginVC = LoginViewController()
loginVC.modalPresentationStyle = .fullScreen
present(loginVC, animated: false)
}
}
}
I have been tinkering with this for days now. I can't seem to figure out the issue. Any help would be really appreciated.
I think your problem is that your WelcomeSpotifyViewController don't have navigaiton controller. You should debug and check value of navigation controller

Problem with delegates removing annotation

I have two screens. The first one (firstViewController) has a mapView with a UITapGestureRecognizer. When the user taps the screen, an annotations is added to the map and the second screen (secondViewController) is presented.
When the user dismisses the secondViewController and comes back to the first one, the annotation should be removed. I know I have to use delegation, but I just can't make it to work.
This is the code I have now:
class firstViewController: UIViewController, AnnotationDelegate {
let mapView = MKMapView()
var temporaryPinArray = [MKPointAnnotation]()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(mapView
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
mapView.addGestureRecognizer(gesture)
secondVC.annotationDelegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
mapView.frame = view.bounds
}
#objc func handleTap(_ gestureReconizer: UILongPressGestureRecognizer) {
let location = gestureReconizer.location(in: mapView)
let coordinates = mapView.convert(location, toCoordinateFrom: mapView)
mapView.removeAnnotations(mapView.annotations)
let pin = MKPointAnnotation()
pin.coordinate = coordinates
temporaryPinArray.removeAll()
temporaryPinArray.append(pin)
mapView.addAnnotations(temporaryPinArray)
// Present secondViewController
let secondVC = SecondViewController()
panel.set(contentViewController: secondVC)
panel.addPanel(toParent: self)
}
func didRemoveAnnotation(annotation: MKPointAnnotation) {
mapView.removeAnnotation(annotation)
}
}
Second View Controller
protocol AnnotationDelegate {
func didRemoveAnnotation(annotation: [MKPointAnnotation])
}
class SecondViewController: UIViewController {
var annotationDelegate: AnnotationDelegate!
let mainVC = firstViewController()
let closeButton: UIButton = {
let button = UIButton()
button.backgroundColor = .grey
button.layer.cornerRadius = 15
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(closeButton)
closeButton.addTarget(self, action: #selector(dismissPanel), for: .touchUpInside)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
closeButton.frame = CGRect(x: view.frame.width-50, y: 10, width: 30, height: 30)
}
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation(annotation: mainVC.temporaryPinArray)
}
}
Thank you so much for your help!
You created a new instance of firstViewController inside SecondViewController. This instance is unrelated to the actual first one:
let mainVC = firstViewController()
This means that temporaryPinArray is different as well. So instead of passing in this unrelated array...
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation(annotation: mainVC.temporaryPinArray)
}
Just change the function to take no parameters instead:
protocol AnnotationDelegate {
func didRemoveAnnotation() /// no parameters
}
#objc func dismissPanel() {
self.dismiss(animated: true, completion: nil)
annotationDelegate.didRemoveAnnotation() /// no parameters
}
And inside firstViewController's didRemoveAnnotation, reference the actual temporaryPinArray.
func didRemoveAnnotation() {
mapView.removeAnnotations(temporaryPinArray) /// the current array
}

WKWebView won't display the webpage I need it to

Given the code below. I can't get the web page "https://baycare.clearstep.health/covid19" to show up. It shows up okay in Safari and I can get other pages to show up in the WKWebView. I have tried implementing all of the navigation and ui delegate methods to try and track down the problem but have failed to find anything.
The URL used to work, but the company has changed something and now it doesn't. Any help is appreciated.
The below is a complete program:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let controller = UIViewController()
.setup { viewController in
WKWebView(frame: viewController.view.bounds)
.setup {
viewController.view.addSubview($0)
$0.uiDelegate = WebViewUIDelegate.instance
$0.navigationDelegate = WebViewDelegate.instance
$0.autoresizingMask = [.flexibleWidth, .flexibleHeight]
$0.load(URLRequest(url: URL(string: "https://baycare.clearstep.health/covid19")!))
}
}
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = controller
window?.makeKeyAndVisible()
return true
}
}
extension NSObjectProtocol {
#discardableResult
func setup(_ fn: (Self) -> Void) -> Self {
fn(self)
return self
}
}
final class WebViewUIDelegate: NSObject, WKUIDelegate {
static let instance = WebViewUIDelegate()
}
final class WebViewDelegate: NSObject, WKNavigationDelegate {
static let instance = WebViewDelegate()
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("This doesn't fire so no error?", error)
}
}
WKWebView is depricated version but still webview will works fine, I had same issue and I was doing same but then instead of WKWenView I used web view
import UIKit
import WebKit
class PolicyVC: UIViewController {
#IBOutlet weak var webView: UIWebView!
var isPolicyView = false
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: UIButton.ButtonType.custom)
button.setImage(UIImage(named: "ic_arrow_left"), for: .normal)
button.addTarget(self, action:#selector(popView), for: .touchUpInside)
button.frame=CGRect(x: 0, y: 0, width: 20, height: 20)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.leftBarButtonItems = [barButton]
setUpUI()
}
#objc func popView(){
self.navigationController?.popViewController(animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.isTranslucent = true
self.navigationController?.navigationBar.isHidden = false
self.navigationController?.navigationBar.backgroundColor = .black
}
func setUpUI(){
if isPolicyView{
let url = URL(string: "https://www.termsfeed.com/privacy-policy/68c4cdaeba7e146a7d72bf57654520e7")
let urlRequest = URLRequest(url: url!)
webView.loadRequest(urlRequest)
}else{
let url = URL(string: "https://www.termsfeed.com/terms-conditions/0001db2c7a1061a55e2f933bb94102de")
let urlRequest = URLRequest(url: url!)
webView.loadRequest(urlRequest)
}
}
}

Can't restore state from code created ViewController

I'm trying to restore ViewController created from code. I'm trying to make very simple example but I don't understand why it doesn't work. An application changes background color of view by clicking on image and should save it when app is stopped. I'm trying to store and restore Bool property, and basing on it app changes views background color. When I'm testing it I'm sending app to background by cmd + shift + h, after that stop app from xcode and open it again, but it doesn't save selected background color.
I've enabled restoration option in AppDelegate
In ViewController I've set restorationIdentifier and restorationClass
Adopted protocol UIViewControllerRestoration in extension to ViewController and implemented encoding and decoding methods
Implemented ViewController restoring method
AppDelegate class
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
}
ViewController class
class ViewController: UIViewController {
var sunny: Bool = true {
didSet {
setColor()
}
}
lazy var colorView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var changeColor: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Change", for: .normal)
button.addTarget(self, action: #selector(change), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.restorationIdentifier = "myVC"
self.restorationClass = ViewController.self
view.backgroundColor = .white
setupUI()
}
#objc func change() {
sunny.toggle()
}
func setColor() {
colorView.backgroundColor = sunny == true ? .yellow : .darkGray
}
}
extension ViewController: UIViewControllerRestoration {
func setupUI() {
view.addSubview(colorView)
NSLayoutConstraint.activate([
colorView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
colorView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
colorView.widthAnchor.constraint(equalToConstant: view.frame.width),
colorView.heightAnchor.constraint(equalToConstant: 500)
])
view.addSubview(changeColor)
NSLayoutConstraint.activate([
changeColor.topAnchor.constraint(equalTo: colorView.bottomAnchor, constant: 10),
changeColor.centerXAnchor.constraint(equalTo: colorView.centerXAnchor),
changeColor.widthAnchor.constraint(equalToConstant: 100),
changeColor.heightAnchor.constraint(equalToConstant: 45)
])
}
override func encodeRestorableState(with coder: NSCoder) {
coder.encode(sunny, forKey: "isSunny")
super.encodeRestorableState(with: coder)
}
override func decodeRestorableState(with coder: NSCoder) {
guard let isSunny = coder.decodeBool(forKey: "isSunny") as? Bool else {return}
sunny = isSunny
setColor()
super.decodeRestorableState(with: coder)
}
static func viewController(withRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
let vc = ViewController()
return vc
}
}

UIVisualEffectView creating unwanted shadow while presenting new view

In my custom presentation transition I've created a new view controller which will pre presented on top of the current active view controller (see screenshot). Somehow there's a shadow behind the blue view controller and I have no idea where it's coming from. Is there a way to stop getting that shadow?
The project is completely empty and has only 2 empty view controllers.
This is the code I'm using:
class ViewController: UIViewController {
let transitionDelegate = TransitionManager()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellowColor()
let button = UIButton(type: .System)
button.frame = CGRectMake(10, 10, 50, 50)
button.addTarget(self, action: "test:", forControlEvents: .TouchUpInside)
button.backgroundColor = UIColor.redColor()
view.addSubview(button)
}
func test(sender: UIButton) {
let destination = UIViewController()
destination.view.backgroundColor = .blueColor()
destination.transitioningDelegate = transitionDelegate
destination.modalPresentationStyle = .Custom
presentViewController(destination, animated: true, completion: nil)
}
}
The code for presenting the view:
class PresentingTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.3
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let presented = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let container = transitionContext.containerView()!
let durations = transitionDuration(transitionContext)
presented.view.alpha = 0
container.addSubview(presented.view)
UIView.animateWithDuration(durations, animations: { presented.view.alpha = 1 }) { transitionContext.completeTransition($0) }
}
}
The code for handling the presenting view controller:
class PresentationController: UIPresentationController {
var background: UIView!
override init(presentedViewController: UIViewController, presentingViewController: UIViewController) {
super.init(presentedViewController: presentedViewController, presentingViewController: presentingViewController)
prepareBackground()
}
func prepareBackground() {
self.background = UIView(frame: presentingViewController.view.bounds)
let blur = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
blur.frame = background.bounds
blur.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
background.addSubview(blur)
let tapRecognizer = UITapGestureRecognizer(target: self, action: "backgroundTapped:")
background.addGestureRecognizer(tapRecognizer)
}
func backgroundTapped(tapRecognizer: UITapGestureRecognizer) {
presentingViewController.dismissViewControllerAnimated(true, completion: nil)
}
override func presentationTransitionWillBegin() {
let container = containerView!
background.frame = container.bounds
background.alpha = 0.0
container.insertSubview(background, atIndex: 0)
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({ _ in self.background.alpha = 1.0 }, completion: nil)
}
override func dismissalTransitionWillBegin() {
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({ _ in self.background.alpha = 0.0 }, completion: nil)
}
override func frameOfPresentedViewInContainerView() -> CGRect {
return containerView!.bounds.insetBy(dx: 100, dy: 100)
}
override func containerViewWillLayoutSubviews() {
background.frame = containerView!.bounds
presentedView()!.frame = frameOfPresentedViewInContainerView()
}
}