I am developing an iOS application where I am streaming a live video from youtube. After exiting the full-screen mode the status bar overlays the navigation bar in all views (as seen in this picture)
I have searched for this problem and found a solution which is adding the following code in a function that is executed after exiting the full-screen mode:
#objc func videoExitFullScreen (_ sender: Any?){
navBar.frame.origin = CGPoint(x: 0, y: 20)
}
This code successfully solved the problem. However, I need to place this code in every controller of my application. When I tried to place it in the app delegate as the following it did not solve the problem:
#objc func videoExitFullScreen (_ sender: Any?){
UINavigationBar.appearance().frame.origin = CGPoint(x: 0, y: 20)
}
Do you have any suggestions where I can place the code in one place and it will solve the problem?
try like this
// create a new class of type UIViewController
class BaseViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.videoExitFullScreen()
}
func videoExitFullScreen (){
navBar.frame.origin = CGPoint(x: 0, y: 20)
}
}
// replace UIViewController to BaseViewController
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
Use Auto Layout and pin your view to Top Layout Guide, or use the new Safe Area Layout Guides. To enable it, in your storyboard's File inspector tick the following option.
Related
I've got some subviews in my navigationBar. A black square, some titles and a blue bar. When pushed to a detail viewController, I want my subviews to hide (animated).
I added the subways to the navigation bar inside the main view controller using
self.navigationController?.navigationBar.addsubView(mySubview).
Currently, it looks like his:
What is the right way to hide (animated) those subviews in the detail viewController?
Your question is very interesting. I never thought about the navigation bar.
When UINavigationController is used as the root controller, all UIViewControllers are stored in its stack, meaning that all UIViewControllers share a navigationBar.
You added mySubView to navigationBar in the first UIViewController. If you want to hide it on the details page, you can search for subviews directly.
The first step, you need to give mySubView a tag, which can be a tag, or it can be a custom type, which is convenient for later judgment.
On the first page
import SnapKit
mySubView = UIView()
mySubView?.tag = 999
mySubView?.backgroundColor = .red
mySubView?.translatesAutoresizingMaskIntoConstraints = false
let navBar = navigationController?.navigationBar
navBar!.addSubview(mySubView!)
mySubView!.snp.makeConstraints { (make) in
make.left.equalTo(navBar!).offset(100)
make.centerY.equalTo(navBar!)
make.width.height.equalTo(50)
}
On the details page, I deleted isHidden and saved navigationBar with the attribute because navigationBar = nil during the gesture. If SnapKit is unfamiliar, take a few more minutes to learn.
var mySubView: UIView? = nil
var navBar: UINavigationBar?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// Do any additional setup after loading the view.
navigationController?.interactivePopGestureRecognizer?.addTarget(self, action: #selector(backGesture))
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
homeNavigationBarStatus()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
homeNavigationBarStatus()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if mySubView == nil {
for view: UIView in (navigationController?.navigationBar.subviews)! {
if view.tag == 999 {
mySubView = view
}
}
}
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
self.detailNavigationBarStatus()
}, completion: nil)
}
func detailNavigationBarStatus(){
changingNavigationBarStatus(progress: 0)
}
func homeNavigationBarStatus(){
changingNavigationBarStatus(progress: 1.0)
}
func changingNavigationBarStatus(progress:CGFloat){
mySubView?.alpha = progress
mySubView?.snp.updateConstraints({ (make) in
make.left.equalTo(navBar!).offset(100 * progress)
})
}
#objc func backGesture(sender: UIScreenEdgePanGestureRecognizer) {
switch sender.state {
case .changed:
let x = sender.translation(in: view).x
progress = x / view.frame.width
changingNavigationBarStatus(progress: progress)
default:
break
}
}
However, using tag values is not elegant enough, you can create a specific class for mySubView, which can also be judged by class.
I have an Intro video section for an app. The storyboard has a segue "toMainMenu" that links to another storyboard view controller.
The IntroVideoVC has two classes inside it:-
class IntroVideoVC: UIViewController {
var videoView: IntroVideoView!
override func viewDidLoad() {
videoView = IntroVideoView(controller: self)
self.view.addSubview(videoView)
let tap = UITapGestureRecognizer(target: self, action: #selector(tappedVideo))
view.addGestureRecognizer(tap)
}
#objc func tappedVideo() {
videoFinished();
}
func videoFinished() {
self.performSegue(withIdentifier: "toMainMenu", sender: self)
}
}
class IntroVideoView: UIView {
init(controller: UIViewController) {
super.init(frame: CGRect(x: 0, y: 0, w: 0, h: 0))
self.controller = controller
...
}
func handleVideo(videoPlayer: AVPlayer) {
...
IntroVideoVC().videoFinished()
}
}
If i tap the video it correctly performs the segue, however if i let the video end which in-turn calls IntroVideoViews -> handleVideo() method i get a crash stating it has no segue with identifier "toMainMenu".
I think i understand why but unsure how to get around the issue being new to swift. I believe it is because handleVideo() is using IntroVideoVC as a singleton and so is losing reference to IntroVideoVC's controller! I maybe (read: likely) be wrong but that is what i feel may be causing this issue.
Just looking for a nudge in the right direction
thanks in advance
You could use protocol to solve that, it would be something like this:
protocol IntroVideoDelegate {
func videoFinished()
}
class IntroVideoVC: UIViewController, IntroVideoDelegate {
var videoView: IntroVideoView!
override func viewDidLoad() {
videoView = IntroVideoView(controller: self)
videoView.videoDelegate = self
self.view.addSubview(videoView)
let tap = UITapGestureRecognizer(target: self, action: #selector(tappedVideo))
view.addGestureRecognizer(tap)
}
#objc func tappedVideo() {
videoFinished();
}
func videoFinished() {
self.performSegue(withIdentifier: "toMainMenu", sender: self)
}
}
class IntroVideoView: UIView {
videoDelegate: IntroVideoDelegate?
init(controller: UIViewController) {
super.init(frame: CGRect(x: 0, y: 0, w: 0, h: 0))
self.controller = controller
...
}
func handleVideo(videoPlayer: AVPlayer) {
...
videoDelegate.videoFinished()
}
}
Remember that the videoView is still a subview to IntroVideoVC, so if the app goes back to this VC the user will see it, so if you don't want that to happen, be sure to remove it with:
videoView.removeFromSuperview()
In the videoFinished() func
If you want to do running a VC func from the view you should get the parent of IntroVideoView, here is how: Given a view, how do I get its viewController? But i'm pretty sure it breaks MVC
import UIKit
class ViewController: UIViewController {
// MARK: -property
// lazy var testBtn: UIButton! = {
// var btn: UIButton = UIButton()
// btn.backgroundColor = UIColor.red
// print("testBtn lazy")
// return btn
// }()
// MARK: -life cycle
override func viewDidLoad() {
super.viewDidLoad()
print("View has loaded")
// set the superView backgroudColor
// self.view.backgroundColor = UIColor.blue
// add testBtn to the superView
// self.view.addSubview(self.testBtn)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("View will appear")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("View has appeared")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("View will disappear")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("View has desappeared")
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
print("SubViews will layout")
// layout subViews
// 'CGRectMake' is unavailable in Swift
// self.testBtn.frame = CGRectMake(100, 100, 100, 100)
// self.testBtn.frame = CGRect(x: 100, y: 100, width: 100, height: 100) // CGFloat, Double, Int
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("SubViews has layouted")
// let testBtn_Width = self.testBtn.frame.width
// print("testBtn's width is \(testBtn_Width)")
}
}
The result:
View has loaded
View will appear
SubViews will layout
SubViews has layouted
SubViews will layout
SubViews has layouted
View has appeared
As you see, I have created a new project and type some simple code.
I didn't change the size of the viewController's view.
Why are "SubViews has layouted" and "SubViews will layout" console two times?
Why are viewDidLayoutSubviews and viewWillLayoutSubviews called two times?
Because whenever setNeedsLayout or setNeedsDisplayInRect is being called internally, LayoutSubviews is also being called (once per run loop) on any given view. This applies for example if the view has been added, scrolled, resized, reused etc.
I need to add Tap Gesture on Navigation Bar or View.
I got the below solution which works perfectly fine.
But removeGestureRecognizer is not removing the gesture and it's breaking the functionality of other back buttons in other view controllers.
How to fix the issue?
var taskTodoOnBar : UITapGestureRecognizer!
override func viewWillAppear(animated: Bool)
{
navigationController?.view.addGestureRecognizer(taskTodoOnBar)
}
override func viewWillDisappear(animated: Bool)
{
navigationController?.view.removeGestureRecognizer(taskTodoOnBar)
}
Or
override func viewWillAppear(animated: Bool)
{
navigationController?.navigationBar.addGestureRecognizer(taskTodoOnBar)
}
override func viewWillDisappear(animated: Bool)
{
navigationController?.navigationBar.removeGestureRecognizer(taskTodoOnBar)
}
When I try to get gestureRecognizers count, It says nil. Then where is the gesture being added ?
override func viewWillDisappear(animated: Bool)
{
print(navigationController!.view.gestureRecognizers!.count)
print(navigationController!.navigationBar.gestureRecognizers!.count)
}
Try using this
Declared gesture as
let tapGesture : UITapGestureRecognizer = UITapGestureRecognizer()
Gesture Handler
#objc func tapHandler(handler: UITapGestureRecognizer){
print("gesture Added")
}
Added in Navigation bar as
override func viewDidLoad()
{
super.viewDidLoad()
tapGesture.numberOfTapsRequired = 1
tapGesture.addTarget(self, action: #selector(VC2.tapHandler(handler:)))
self.navigationController?.view.addGestureRecognizer(tapGesture)
}
Removed as
override func viewWillDisappear(_ animated: Bool) {
for gesture in (navigationController?.view.gestureRecognizers)! {
if gesture == tapGesture {
navigationController?.view.removeGestureRecognizer(tapGesture)
print("removed")
}
}
}
Updated Answer for - gesture count prints nil
console Output :
After help from iOS Geek, I figured out that, gestureRecognizers!.count was 2 in ViewdDidLoad but was nil inside viewWillDisappear.
Then I dug more and discovered that I had written the custom code for my back button.
So in such case, we Should removeGestureRecognizer before popToViewController
So this is for all whom I wish not to make mistake like me while using the custom back button.
func backBarBtnFnc(sender: UIBarButtonItem)
{
navigationController?.navigationBar.removeGestureRecognizer(taskTodoOnBar)
// CodTdo ...
self.navigationController!.popToViewController(VC2, animated: true)
}
I implemented ENSwiftSideMenu in my app, (you can just use the sample project it provided, because it has the same problem,) and when I rotate the iPhone to portrait mode, the menu tableView has a gap between the nav bar and the cells
I found the problem. It's at the following file: MyMenuTableViewController.swift
tableView.contentInset = UIEdgeInsetsMake(64.0, 0, 0, 0)
So I replaced the height of 64 with the height of the navbar:
override func viewDidLoad() {
super.viewDidLoad()
tableView.contentInset = UIEdgeInsetsMake(self.navigationController!.navigationBar.frame.height, 0, 0, 0) // Error
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
tableView.contentInset = UIEdgeInsetsMake(self.navigationController!.navigationBar.frame.height, 0, 0, 0)
}
When I run the app, I get the following error at the code in viewDidLoad:
fatal error: unexpectedly found nil while unwrapping an Optional value
That error before the first page loads (at the splash screen). What can I do to fix that? Or if there's a better way to remove the gap please let me know. I already tried adding the following in viewDidLoad:
self.automaticallyAdjustsScrollViewInsets = true
And that doesn't do anything.
Ok, I've been looking how ENSwiftSideMenu works. It looks like your side menu isn't added to the navigation stack, so if you call self.navigationController, it returns nil always.
Also, ENSwiftSideMenu uses a custom navigation controller: ENSideMenuNavigationController that have a property called sideMenu, which unfortunately, inherits from NSObject (not from UIViewController). So basically, what I had to do was to change MyNavigationController class so it holds a reference to the MyMenuTableViewController used as a side menu, and apply content insets to it's tableview based on the ENSideMenuNavigationController's navigation bar size.
Lastly, you need to apply side table view's contentInset after the rotation was made, so you need to observe UIDeviceOrientationDidChangeNotification notification.
This is how MyNavigationController class should be to work:
class MyNavigationController: ENSideMenuNavigationController, ENSideMenuDelegate {
private let menuTableViewController = MyMenuTableViewController()
override func viewDidLoad() {
super.viewDidLoad()
sideMenu = ENSideMenu(sourceView: self.view, menuViewController: menuTableViewController, menuPosition:.Left)
//sideMenu?.delegate = self //optional
sideMenu?.menuWidth = 180.0 // optional, default is 160
//sideMenu?.bouncingEnabled = false
// make navigation bar showing over side menu
view.bringSubviewToFront(navigationBar)
menuTableViewController.tableView.contentInset = UIEdgeInsetsMake(self.navigationBar.frame.height + self.navigationBar.frame.origin.y, 0, 0, 0)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "rotated", name: UIDeviceOrientationDidChangeNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIDeviceOrientationDidChangeNotification, object: nil)
}
func rotated() {
menuTableViewController.tableView.contentInset = UIEdgeInsetsMake(self.navigationBar.frame.height + self.navigationBar.frame.origin.y, 0, 0, 0)
}
// MARK: - ENSideMenu Delegate
func sideMenuWillOpen() {
println("sideMenuWillOpen")
}
func sideMenuWillClose() {
println("sideMenuWillClose")
}
func sideMenuDidClose() {
println("sideMenuDidClose")
}
func sideMenuDidOpen() {
println("sideMenuDidOpen")
}
}