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")
}
}
Related
I try to resize the height of a UITextView-field when the keyboard appears (iOS 14.2, Xcode 12.3). The distance from the bottom of the UITextView to the save area is 90 and hence the lower part of it is hidden by the keyboard and can't be seen while editing.
I tried it with the solution shown here: Resize the UITextView when keyboard appears
Accordingly, my code is as follows:
class EditierenVC: UIViewController, UITextFieldDelegate, UITextViewDelegate {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(false)
NotificationCenter.default.addObserver(
self,
selector: #selector(EditierenVC.handleKeyboardDidShow(notification:)),
name: UIResponder.keyboardDidShowNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(EditierenVC.handleKeyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
}
#objc func handleKeyboardDidShow(notification: NSNotification) {
guard let endframeKeyboard = notification
.userInfo![UIResponder.keyboardFrameEndUserInfoKey]
as? CGRect else { return }
textfeld.contentInset = UIEdgeInsets(
top: 0.0,
left: 0.0,
bottom: endframeKeyboard.size.height-85,
right: 0.0
)
view.layoutIfNeeded()
}
#objc func handleKeyboardWillHide() {
textfeld.contentInset = .zero
view.layoutIfNeeded()
}
//**************************
// MARK: - Views
//**************************
#IBOutlet weak var textfeld: UITextView!
Unfortunately the inset-size is not changed, when the keyboard appears and the text is partly hidden.
Has anyone an idea, why it is not working?
Thanks for your support
I could not come up with a solution by using content inset but I can suggest another way.
If you add bottom constraint to the textView and create outlet for that, you can change its constant value in notifications;
#objc func handleKeyboardDidShow(notification: NSNotification) {
guard let endframeKeyboard = notification
.userInfo![UIResponder.keyboardFrameEndUserInfoKey]
as? CGRect else { return }
textViewBottomConstraint.constant = endframeKeyboard.size.height-85
}
#objc func handleKeyboardWillHide() {
textViewBottomConstraint.constant = // set the previous value here
}
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 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.
I had this code in a UITableViewController and it worked perfectly.
func setupSearchBar() {
let searchBar: UISearchBar = searchController.searchBar
tableView.tableHeaderView = searchBar
let point = CGPoint(x: 0, y: searchBar.frame.size.height)
tableView.setContentOffset(point, animated: true
}
Now I'm refactoring my code to fit more of an MVC style architecture. What I did is create a UITableView in the View class:
class View: UIView {
lazy var tableView: UITableView = {
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
return table
}()
func configureView() {
// tableView
addSubview(tableView)
tableView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
tableView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
tableView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
tableView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
}
}
and then use the View class in my ViewController:
class ViewController: UIViewController {
var newView: View! { return self.view as! View }
override func loadView() {
view = View(frame: UIScreen.main.bounds)
newView.configureView()
}
override func viewDidLoad() {
super.viewDidLoad()
setupSearchBar()
}
func setupSearchBar() {
let searchBar: UISearchBar = searchController.searchBar
newView.tableView.tableHeaderView = searchBar
let point = CGPoint(x: 0, y: searchBar.frame.size.height)
newView.tableView.setContentOffset(point, animated: true)
}
The tableView shows up no problem and everything else is fine. The only thing that's not working is the setContentOffset is being called, but it's not offsetting the content. I want the searchbar to be hidden by default when the user first opens this viewController (similar to iMessage), but after I moved the code from a UITableViewController to separate files (UIView + UIViewController) like in this example, the searchbar always shows by default.
I'm not sure why it's not working. Any help would be greatly appreciated.
It's probably a timing problem relative to layout. Instead of calling setUpSearchBar in viewDidLoad, do it later, in viewDidLayoutSubviews, when initial layout has actually taken place. This method can be called many times, so use a flag to prevent it from being called more than once:
var didSetUp = false
override func viewDidLayoutSubviews() {
if !didSetUp {
didSetUp = true
setUpSearchBar()
}
}
Also: Your animated value is wrong:
newView.tableView.setContentOffset(point, animated: true)
You mean false. You don't want this movement to be visible. The table view should just appear with the search bar out of sight.
I was first having problems with textfields on the bottom of the screen because the keyboard would cover them. Now that I fixed that problem I have a new one. When I try to enter text in a textfield that is at the top of the screen, the screen rises and does not let me see what I'm typing.
I think what I would ideally like to do is change how much the keyboard pushes the screen up. Below is the code I used for the initial change.
I just started learning to develop two weeks ago so I'm still getting familiar with all the syntax and functions.
var kbHeight: CGFloat!
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated:Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
if let keyboardSize = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
kbHeight = keyboardSize.height
self.animateTextField(true)
}
}
}
func keyboardWillHide(notification: NSNotification) {
self.animateTextField(false)
}
func animateTextField(up: Bool) {
var movement = (up ? -kbHeight : kbHeight)
UIView.animateWithDuration(0.3, animations: {
self.view.frame = CGRectOffset(self.view.frame, 0, movement)
})
}
It's a suggestion
Instead of setting View's frame. put the view inside a UIScrollView.
then inside your keyboardWillShow and keyboardWillHide methods adjust the scrollview frame accordingly.
and set the scrollview's content offset using the "scrollRectToVisible" inside the the textfield delegate method.
- (void) textFieldDidBeginEditing:(UITextField *)textField
{
CGRect rectText = textField.frame;
self.scrollVIew scrollRectToVisible:rectText animated:TRUE];
}