Navigationbar change (sometimes) position - ios

I have problems with the position of my navigation bar.
Sometimes in random views on random phones my navigation bar hasn't the right position. It should be approximately 30-35 pixels lower.
The stupid thing is that I can't reproduce the bug. It seems that the bug is appearing when the phone have a lot of processes in the background.
The logic behind: My initial view is a Container View. This Container View implement a Navigation View Controller. I can't change this, because I undertook this project. If it does not have to be, I don't want to change the basic structure.
I know for you it is like a clairvoyant, because I can't give more information. I wish I could...
But maybe you know how to set the navigation in a fix position? Or you know this kind of bug and fixed it already?
I am thankful for some ideas.
Update
Thanks for reply.
I implement this code, I can't say, whether it's working, because my bug doesn't appear always. We will see and I will give reply as fast as possible.
func fixPositionOfNavigationBar() {
self.edgesForExtendedLayout = .Top
self.extendedLayoutIncludesOpaqueBars = true
if let navigationBar = self.navigationController?.navigationBar {
positionForBar(navigationBar)
}
}
When I know for what I am searching I found similar problems to mine.. maybe it helps:
One possible solution
Snd possible solution

Try conforming view controller to UIBarPositioningDelegate protocol by this:
func positionForBar(bar: UIBarPositioning) -> UIBarPosition {
return UIBarPosition.TopAttached
}

Related

UIRefreshControl is flickering and transitions are abrupt

I'd like to set up a UIRefreshControl in my apps' main table view controller to trigger a refresh function to (you probably guessed it by now) refresh it.
Adding it isn't the problem, I'm able to set it up through the storyboard (by enabling refreshing) or in the controller (by using self.refreshControl = UIRefreshControl()).
When it gets triggered by the user it starts refreshing normally, but when I trigger it through self.refreshControl.beginRefreshing() the control isn't displayed in the navigation bar, until I scroll down (like you would normally trigger it)
But that's not even the weirdest part. When it's finally visible to the user there is a constant flicker to it and after a while it's not visible at all.
For the self.refreshControl.endRefreshing() method, the animation is really abrupt any choppy and sometimes the navigation bar doesn't scroll up again (leaving an empty space at the top)
Here is a gif, that should summarise my problem
I use swift 4.2, the application was tested on different simulators (running iOS 12.1) and my iPhone X (running iOS 12.1.2) and the release target is iOS 10.0.
I've already done research on my problem, but no one seems to have the exact issue (at least the flickering) or the solutions don't work for me.
This is how my view controller is set up:
class MyViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl = UIRefreshControl()
self.refreshControl?.addTarget(self, action: #selector(reload), for: .valueChanged)
self.refreshControl.beginRefreshing()
}
#objc func reload() {
DispatchQueue(label: "update").async {
sleep(10)
DispatchQueue.main.async {
self.refreshControl?.endRefreshing()
}
}
}
}
Thanks for reading, I really hope someone can help me.
Update
I just discovered that the issue only occurs with a non-translucent navigation bar with large title, so picking a translucent navigation bar instead of the opaque is a solution (sadly not one I would like to live with). Maybe someone has an approach to having a functional refresh control with an opaque navigation bar.
Additionally I submitted a bug report to Apple, because something like that should not happen in the first place, I'll update this post when they get back to me.

Navigation Bar black line with Large title

Thanks for taking the time to read.
Firstly, let me say that I have tried to do my due diligence in searching for a solution to the problem, but to no avail, and it is stressing me out! So, if someone finds an answer could you please point me in the direction :)
The problem I am facing is to do with the black line appearing underneath the Navigation Bar. Now, I am very much aware of the setting a default image/shadow image property of the navigation bar to correct this, and as such, I created my own extension to implement it (shown below)
func hideHairline(_ state:Bool) {
if state {
self.shadowImage = UIImage()
self.setBackgroundImage(UIImage(), for: .any, barMetrics: .default)
}else{
self.setBackgroundImage(UINavigationBar.appearance().backgroundImage(for: UIBarMetrics.default), for:UIBarMetrics.default)
self.shadowImage = nil
}
}
However, something seems to be misbehaving, and I am getting a black line appearing underneath my navigation bar (see below)
Whats more strange, is that the simulator does not show this line on the same screen. The simulator is set to be pixel accurate. (as shown below)
I think it may have something to do with the way I am changing from a previous VC. I am changing from having the navigation bar hidden, to shown, and it is set to prefersLargeTitles. Now, if I set the navigation bar, not to use the large titles, the black line doesn't show. Also, if I push to the next view controller, and then pop back, the line is not there (on the pushed controller, or the newly presented popped)
At this point, I am mainly just wondering why this is happening? I mean, I must have done something wrong. I wrote a setup extension, just to keep the code away from the VC, probably isn't the best coding practice, but hey!
self.hideHairline(true)
self.titleTextAttributes = [NSAttributedStringKey.foregroundColor : Constants.navigationBarTextColor]
self.largeTitleTextAttributes = self.titleTextAttributes
self.barTintColor = Constants.navigationBar
self.tintColor = Constants.navigationBarTint
self.isTranslucent = false
self.backgroundColor = Constants.navigationBar
self.prefersLargeTitles = true
The Constants is a class that holds all of the static variables in my app, such as colo(u)rs. Have I just made some stupid mistake, or is there a bug with large titles?
If you need any further information, please, just ask :)
Thanks
So, after taking a break for the evening, I have found a solution. I am going to leave this post up, for anyone else that might encounter a similar problem in the future.
It is to do with the view of the UINavigationController itself. I found another stack overflow post that answered it (link here)
All I had to do was:
self.navigationController?.view.backgroundColor = .white
change the colo(u)r to whatever you need it to be.
Simple solution, and I cannot believe it took me so long to figure out.
All the best.

Disabling Dock in iOS programmatically

Is it possible to disable the dock that pops up in iOS?
This is my View Controller. Notice that it has a draggable view controller in the footer.
But when I try to pull it up quickly, the dock shows up:
Is there any way to disable it?
I think the closest you can get is iOS 11's preferredScreenEdgesDeferringSystemGestures(), which will show an indicator at the bottom but not pull up the dock on the first swipe. For example, in your view controller:
override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
return [.bottom]
}
In my experience it still eats the swipe gesture, but it still gives the user a second chance to hit the right target.
On iOS <11 however, this behavior can only be obtained by hiding the status bar.
Edit:
Usually when faced with implementing a design choice like this, I try to offer a second, non-interfering gesture as a backup, such as a tap in that area, that has the same effect.
As in iOS 11, you cannot disable the dock in an application, nor in Settings. I'd suggest providing a larger area for swiping up from the bottom.
Normally such conflicts should be avoided, as they degrade user experience: how do you know that the user does not actually want to use the dock?
But if you really want, you can override the preferredScreenEdgesDeferringSystemGestures() method in the root controller to specify which edges should NOT (immediately) trigger system gestures.
e.g.
override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
return .bottom
}

Tweak UI Navigation on iOS 11

I have a problem with a navigation bar in iOS 11.
I use this code:
[UINavigationBar appearance].prefersLargeTitles = YES;
...to set a new style for my app. It works well untill a user pull to refresh on the table view; then it breaks.
This is before pull-to-refresh:
...and this is _after:
Note: I use the table view controller's built-in pull-to-refresh control.
I searched for a solution but it still eludes me. If someone knows how to fix this please drop some advice.
Thanks for the support :)
I have catch this bug too, and we have found the solution.
You must constraint your UITableView to superview (contentView of your view controller), after that large title and all related views starts to work correct.
Like this:
While I do not claim this is the solution for every situation the error occurs in, setting the navigationBar's isTranslucent property to true (which is also the default value) fixed the problem for me.
If you want to keep your navigation bar non-translucent you can use the following code:
navigationBar.barStyle = .blackOpaque

Embedding Share Icons Inline Within Another View

I am attempting to implement a "Share" feature in my iOS app with a similar look and feel to that of the Google Photos iOS app:
The bottom two rows of icons are what I care about. They look nearly identical to those displayed when using the UIActivityViewController.
In the Google Photos app, these icons appear inline with the "select photos" portion of the screen (e.g. you can still interact with the upper portion of the screen). However, the documentation for the UIActivityViewController states that "On iPhone and iPod touch, you must present [the view controller] modally."
This is the difference that is really important to me -- I'd like for the "share" icons to display inline with the rest of my content, rather than having a modal that is displayed on top of my content.
Is it possible to use the UIActivityViewController to achieve a similar effect shown in the screenshot above? If not, is there a recommended approach that I might use to implement this sort of functionality?
As discussed in another answer, reverse engineering UIActivityViewController is the only option in order to be able to achieve a similar effect. I tried this using iPhone 6s - 10.3 Simulator. The following findings may not be accurate for iOS 9.x or 11.x or above.
A. Find out all internal variables for UIActivityViewController
var variablesCount: UInt32 = 0
let variables = class_copyIvarList(UIActivityViewController.self, &variablesCount)
for i in 0..<variablesCount {
if let variable = variables?[Int(i)] {
let name = String(cString: ivar_getName(variable))
let typeEncoding = String(cString: ivar_getTypeEncoding(variable))
print("\(name)\n\(typeEncoding)\n\n")
}
}
free(variables)
The ones those got my attention at first sight are (in order) -
_activityViewController
#"UIViewController"
_contentController
#"_UIActivityViewControllerContentController"
_activityAlertController
#"UIAlertController"
On inspecting them further, I found out that _contentController is the one we should be looking for. We need to look one level deeper in hierarchy for UICollectionViewController to get to where we want to be.
if let activityContentController = activityVC.value(forKeyPath: "_contentController") as? UIViewController {
print("Found _contentController!")
for child in activityContentController.childViewControllers {
print(String(describing: child))
if child is UICollectionViewController {
print("Found UICollectionViewController!")
break
}
}
}
Why did I look for UICollectionViewController?
Debug View Hierarchy has the answer for this.
I tried adding this as a childViewController to my UIViewController -
self.addChildViewController(child)
child.didMove(toParentViewController: self)
self.view.addSubview(child.view)
child.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
child.view.topAnchor.constraint(equalTo: self.view.topAnchor),
child.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
child.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
child.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
IT SHOWS UP CORRECTLY ONLY IF YOU HAVE LOADED/PRESENTED UIActivityViewController FIRST.
I was able to achieve this using a silent present/dismiss call -
self.present(activityVC, animated: false, completion: {
self.dismiss(animated: false, completion: nil)
})
IS THIS APP STORE SAFE? - Most likely not.
As soon as you start stealing the view(s) or viewController(s) from UIKit's standard components, the behavior is not stable and it will break with upcoming updates for sure.
What Google Photos has is the result of way more advanced reverse engineering. In above implementation, you can't see More option screen. The hierarchy UIActivityViewController expects is broken.
Hope this helps.
Okay, I thought about this and I did some intensive research on the web but nobody ever seemed to needed to modify it like you want. So here are my guesses how Google engineers solved this:
They reverse engineered the UIActivityViewController and call some private APIs to get the same icons to show up and the reordering controllers
They use the UIViewController transitioning API and hack the view hierarchy of a modally presented UIActivityViewController, removing the cancel button and adding some custom views on top of its view
An indicator for the second option could be that the top of the presented "sheet" has a white background while the bottom has the greyish color.
Unfortunately I'm not very fit with the transitioning API as I'm just about to learn it but my understanding is that you can provide a custom object as the transitioning delegate.
This object then gets called when you present/dismiss or push/pop a UIViewController. It will get a reference to both, the presenting and the presented view controller and you can have fun with both of their views.
This should make it "quiet" easy to remove and add some subviews, change frames and colors etc while still having all the default behavior.
I hope this answer helps you to achieve what you want. However, be aware that the structure of the controller could change at any time so always make sure to also test agains betas so that you don't get caught by surprise when apple releases an update which breaks your UI. :)

Resources