My VC starts with stackView attached with Align Bottom to Safe Area .
I have tabBar, but in the beginning is hidden tabBar.isHidden = true.
Later when the tabBar appears, it hides the stackView
So I need function that refresh constraints after tabBar.isHidden = false
When I start the app with tabBar.isHidden = false the stackView is shown properly.
Tried with every function like: stackView.needsUpdateConstraints() , updateConstraints() , setNeedsUpdateConstraints() without success.
Now I'm changing the bottom programatically, but when I switch the tabBarIndex and return to that one with changed bottom constraints it detects the tabBar and lifts the stackView under another view (which is not attached with constraints). Like is refreshing again the constraints. I'm hiding and showing this stackView with constrains on/off screen.
I need to refresh constraints after tabBar.isHidden = false, but the constraints don't detect the appearance of the tabBar.
As I mention switching between tabBars fixes the issue, so some code executes to detecting tabBar after the switch. Is anyone know this code? I tried with calling the methods viewDidLayoutSubviews and viewWillLayoutSubviews without success... Any suggestions?
This amateur approach fixed my bug... :D
tabBarController!.selectedIndex = 1
tabBarController!.selectedIndex = 0
Or with an extension
extension UITabBarController {
// Basically just toggles the tabs to fix layout issues
func forceConstraintRefresh() {
// Get the indices we need
let prevIndex = selectedIndex
var newIndex = 0
// Find an unused index
let items = viewControllers ?? []
find: for i in 0..<items.count {
if (i != prevIndex) {
newIndex = i
break find
}
}
// Toggle the tabs
selectedIndex = newIndex
selectedIndex = prevIndex
}
}
Usage (called when switching dark / light mode):
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
tabBarController?.forceConstraintRefresh()
}
If you want to update view's layout, you can try layoutIfNeeded() function.
after updating stackView constraints call this method:
stackView.superview?.layoutIfNeeded()
Apple's Human Interface Guidelines indicate that one should not mess around with the Tab Bar, which is why (I'm guessing) setting tabBar.isHidden doesn't properly update the rest of the view hierarchy.
Quick searching comes up with various UITabBarController extensions for showing / hiding the tab bar... but they all appear to push the tabBar down off-screen, rather than setting its .isHidden property. May or may not be suitable for your use.
I'm assuming from your comments that your VC in tab index 0 has a button (or some other action) to show / hide the tabBar?
If so, here is an approach that may do the job....
Add this enum in your project:
enum TabBarState {
case toggle, show, hide
}
and put this func in that view controller:
func showOrHideTabBar(state: TabBarState? = .toggle) {
if let tbc = self.tabBarController {
let b: Bool = (state == .toggle) ? !tbc.tabBar.isHidden : state == .hide
guard b != tbc.tabBar.isHidden else {
return
}
tbc.tabBar.isHidden = b
view.frame.size.height -= 0.1
view.setNeedsLayout()
view.frame.size.height += 0.1
}
}
You can call it with:
// default: toggles isHidden
showOrHideTabBar()
// toggles isHidden
showOrHideTabBar(state: .toggle)
// SHOW tabBar (if it's hidden)
showOrHideTabBar(state: .show)
// HIDE tabBar (if it's showing)
showOrHideTabBar(state: .hide)
I would expect that simply pairing .setNeedsLayout() with .layoutIfNeeded() after setting the tabBar's .isHidden property should do the job, but apparently not.
The quick frame height change (combined with .setNeedsLayout()) does trigger auto-layout, though, and the height change is not visible.
NOTE: This is the result of very brief testing, on one device and one iOS version. I expect it will work across devices and versions, but I have not done complete testing.
I may be getting lost in a glass of water as I am not an experienced developer but I cannot seem to be able to implement a simple override to modify the size of an NSTabView item.
I have a Tab View Controller (Style = toolbar)
I have a Tabless Tab View
I have 3 Tab Items. For testing I have only subclassed one of them to the subclass below
I have created a new subclass of NSTabViewItem: MyTabViewItem and subclassed one of the 3 tab Items. The code is:
import Cocoa
class MyTabViewItem: NSTabViewItem {
override func drawLabel(_ shouldTruncateLabel: Bool, in labelRect: NSRect) {
var size = self.sizeOfLabel(false)
size.width = 180
print("Draw!!")
}
override func sizeOfLabel(_ computeMin: Bool) -> NSSize {
var size = super.sizeOfLabel(false)
size.width = 180
print("Draw!!")
return size
}
}
Everything works, except the subclassing. The Tabs appear, they do operate by switching the views and the program runs as it should. Except that it does not resize the Tab Item. The code in the subclass MyTabViewItem is never reached (it never prints Draw!! as it should.
I cannot understand what I am missing here. I have not read of any IB connection to make (and I cannot seem to be able to connect the Tab Items anyways). Please apologise if it isa trivial question but I have searched everywhere and not found anything to help me.
Thank you
You said:
I have a Tabless Tab View
This is your problem. An NSTabView only asks an NSTabViewItem to drawLabel if the NSTabView itself is responsible for drawing the tab bar, but you have a “Tabless” tab view. (“Tabless” is the default style when you drag an NSTabViewController into a storyboard.)
You also said:
I have a Tab View Controller (Style = toolbar)
So you don't even want the tab view to draw a tab bar; you want items in the window toolbar to select tabs (like in Xcode's preference window).
Your ability to customize the toolbar items created for your tabs is limited. You can subclass NSTabViewController and override toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:, like this:
override func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
let toolbarItem = super.toolbar(toolbar, itemForItemIdentifier: itemIdentifier, willBeInsertedIntoToolbar: flag)
if
let toolbarItem = toolbarItem,
let tabViewItem = tabViewItems.first(where: { ($0.identifier as? String) == itemIdentifier.rawValue })
{
toolbarItem.label = "\(tabViewItem.label) 😀"
}
return toolbarItem
}
But I found that making other changes didn't work well:
Setting toolbarItem.image didn't work well for me.
Setting toolbarItem.view made the item stop receiving clicks.
Note that the minSize and maxSize properties are only used if toolbarItem.view is set.
Your best bet is probably to manage the toolbar yourself, without trying to use NSTabViewController's support.
I have also subclassed the NSTabViewController as follows:
import Cocoa
class MyTabViewController: NSTabViewController {
#IBOutlet weak var TradingTabItem: MyTabViewItem!
override func viewDidLoad() {
super.viewDidLoad()
print("Loaded Tab View")
TradingTabItem.label = "New"
// Do view setup here.
}
}
What happens now is that the tab item in my subclass (the only one of the 3 I subclassed) does change its label string to New. However, even if I have added the item as an IBOutlet here, it still does not change seize (and the overridden sizeOfLabel function is not reached).
Anyone having issue with the iPhone X simulator around the UITabBar component?
Mine seem to be rendering the icons and title on top of each other, I'm not sure if I'm missing anything, I also ran it in the iPhone 8 simulator, and one actual devices where it looks fine just as shown on the story board.
iPhone X:
iPhone 8
I was able to get around the problem by simply calling invalidateIntrinsicContentSize on the UITabBar in viewDidLayoutSubviews.
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self.tabBar invalidateIntrinsicContentSize];
}
Note: The bottom of the tab bar will need to be contained to the bottom of the main view, rather than the safe area, and the tab bar should have no height constraint.
Answer provided by VoidLess fixes TabBar problems only partially. It fixes layout problems within the tabbar, but if you use viewcontroller that hides the tabbar, the tabbar is rendered incorrectly during animations (to reproduce it is best 2 have 2 segues - one modal and one push. If you alternate the segues, you can see the tabbar being rendered out of place). The code bellow fixes both problems. Good job apple.
class SafeAreaFixTabBar: UITabBar {
var oldSafeAreaInsets = UIEdgeInsets.zero
#available(iOS 11.0, *)
override func safeAreaInsetsDidChange() {
super.safeAreaInsetsDidChange()
if oldSafeAreaInsets != safeAreaInsets {
oldSafeAreaInsets = safeAreaInsets
invalidateIntrinsicContentSize()
superview?.setNeedsLayout()
superview?.layoutSubviews()
}
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
if #available(iOS 11.0, *) {
let bottomInset = safeAreaInsets.bottom
if bottomInset > 0 && size.height < 50 && (size.height + bottomInset < 90) {
size.height += bottomInset
}
}
return size
}
override var frame: CGRect {
get {
return super.frame
}
set {
var tmp = newValue
if let superview = superview, tmp.maxY !=
superview.frame.height {
tmp.origin.y = superview.frame.height - tmp.height
}
super.frame = tmp
}
}
}
Objective-C code:
#implementation VSTabBarFix {
UIEdgeInsets oldSafeAreaInsets;
}
- (void)awakeFromNib {
[super awakeFromNib];
oldSafeAreaInsets = UIEdgeInsetsZero;
}
- (void)safeAreaInsetsDidChange {
[super safeAreaInsetsDidChange];
if (!UIEdgeInsetsEqualToEdgeInsets(oldSafeAreaInsets, self.safeAreaInsets)) {
[self invalidateIntrinsicContentSize];
if (self.superview) {
[self.superview setNeedsLayout];
[self.superview layoutSubviews];
}
}
}
- (CGSize)sizeThatFits:(CGSize)size {
size = [super sizeThatFits:size];
if (#available(iOS 11.0, *)) {
float bottomInset = self.safeAreaInsets.bottom;
if (bottomInset > 0 && size.height < 50 && (size.height + bottomInset < 90)) {
size.height += bottomInset;
}
}
return size;
}
- (void)setFrame:(CGRect)frame {
if (self.superview) {
if (frame.origin.y + frame.size.height != self.superview.frame.size.height) {
frame.origin.y = self.superview.frame.size.height - frame.size.height;
}
}
[super setFrame:frame];
}
#end
There is a trick by which we can solve the problem.
Just put your UITabBar inside a UIView.
This is really working for me.
Or you can follow this Link for more details.
override UITabBar sizeThatFits(_) for safeArea
extension UITabBar {
static let height: CGFloat = 49.0
override open func sizeThatFits(_ size: CGSize) -> CGSize {
guard let window = UIApplication.shared.keyWindow else {
return super.sizeThatFits(size)
}
var sizeThatFits = super.sizeThatFits(size)
if #available(iOS 11.0, *) {
sizeThatFits.height = UITabBar.height + window.safeAreaInsets.bottom
} else {
sizeThatFits.height = UITabBar.height
}
return sizeThatFits
}
}
I added this to viewWillAppear of my custom UITabBarController, because none of the provided answers worked for me:
tabBar.invalidateIntrinsicContentSize()
tabBar.superview?.setNeedsLayout()
tabBar.superview?.layoutSubviews()
I had the same problem.
If I set any non-zero constant on the UITabBar's bottom constraint to the safe area:
It starts working as expected...
That is the only change I made and I have no idea why it works but if anyone does I'd love to know.
Fixed by using subclassed UITabBar to apply safeAreaInsets:
class SafeAreaFixTabBar: UITabBar {
var oldSafeAreaInsets = UIEdgeInsets.zero
#available(iOS 11.0, *)
override func safeAreaInsetsDidChange() {
super.safeAreaInsetsDidChange()
if oldSafeAreaInsets != safeAreaInsets {
oldSafeAreaInsets = safeAreaInsets
invalidateIntrinsicContentSize()
superview?.setNeedsLayout()
superview?.layoutSubviews()
}
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
if #available(iOS 11.0, *) {
let bottomInset = safeAreaInsets.bottom
if bottomInset > 0 && size.height < 50 {
size.height += bottomInset
}
}
return size
}
}
Moving the tab bar 1 point away from the bottom worked for me.
Of course you'll get a gap by doing so which you'll have to fill in some way, but the text/icons are now properly positioned.
Aha! It's actually magic!
I finally figured this out after hours of cursing Apple.
UIKit actually does handle this for you, and it appears that the shifted tab bar items are due to incorrect setup (and probably an actual UIKit bug). There is no need for subclassing or a background view.
UITabBar will "just work" if it is constrained to the superview's bottom, NOT to the bottom safe area.
It even works in Interface builder.
Correct Setup
In interface builder, viewing as iPhone X, drag a UITabBar out to where it snaps to the bottom safe area inset. When you drop it, it should look correct (fill the space all the way to the bottom edge).
You can then do an "Add Missing Constraints" and IB will add the correct constraints and your tab bar will magically work on all iPhones! (Note that the bottom constraint looks like it has a constant value equal to the height of the iPhone X unsafe area, but the constant is actually 0)
Sometimes it still doesn't work
What's really dumb is that you can actaully see the bug in IB as well, even if you add the exact constraints that IB adds in the steps above!
Drag out a UITabBar and don't snap it to the bottom safe area inset
Add leading, trailing and bottom constraints all to superview (not safe area)
Weirdly, this will fix itself if you do a "Reverse First And Second Item" in the constraint inspector for the bottom constraint. ¯_(ツ)_/¯
Solved for me by calling [tabController.view setNeedsLayout]; after dismissing the modal in completion block.
[vc dismissViewControllerAnimated:YES completion:^(){
UITabBarController* tabController = [UIApplication sharedApplication].delegate.window.rootViewController;
[tabController.view setNeedsLayout];
}];
The UITabBar is increasing in height to be above the home button/line, but drawing the subview in its original location and overlaying the UITabBarItem over the subview.
As a workaround you can detect the iPhone X and then shrink the height of the view by 32px to ensure the tab bar is displayed in the safe area above the home line.
For example, if you're creating your TabBar programatically replace
self.tabBarController = [[UITabBarController alloc] init];
self.window.rootViewController = self.tabBarController;
With this:
#define IS_IPHONEX (([[UIScreen mainScreen] bounds].size.height-812)?NO:YES)
self.tabBarController = [[UITabBarController alloc] init];
self.window.rootViewController = [[UIViewController alloc] init] ;
if(IS_IPHONEX)
self.window.rootViewController.view.frame = CGRectMake(self.window.rootViewController.view.frame.origin.x, self.window.rootViewController.view.frame.origin.y, self.window.rootViewController.view.frame.size.width, self.window.rootViewController.view.frame.size.height + 32) ;
[self.window.rootViewController.view addSubview:self.tabBarController.view];
self.tabBarController.tabBar.barTintColor = [UIColor colorWithWhite:0.98 alpha:1.0] ;
self.window.rootViewController.view.backgroundColor = [UIColor colorWithWhite:0.98 alpha:1.0] ;
NOTE: This could well be a bug, as the view sizes and tab bar layout are set by the OS. It should probably display as per Apple's screenshot in the iPhone X Human Interface Guidelines here: https://developer.apple.com/ios/human-interface-guidelines/overview/iphone-x/
My case was that I had set a custom UITabBar height in my UITabBarController, like this:
override func viewWillLayoutSubviews() {
var tabFrame = tabBar.frame
tabFrame.size.height = 60
tabFrame.origin.y = self.view.frame.size.height - 60
tabBar.frame = tabFrame
}
Removing this code was the solution for the TabBar to display correctly on iPhone X.
The simplest solution I found was to simply add a 0.2 pt space between the bottom of the tab bar and the bottom of the safeAreaLayoutGuide.bottomAnchor like so.
tabBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -0.2)
I was having the same issue when I was trying to set the frame of UITabBar in my custom TabBarController.
self.tabBar.frame = CGRectMake(0, self.view.frame.size.height - kTabBarHeight, self.view.frame.size.width, kTabBarHeight);
When I just adjusted it to the new size the issue went away
if(IS_IPHONE_X){
self.tabBar.frame = CGRectMake(0, self.view.frame.size.height - kPhoneXTabBarHeight, self.view.frame.size.width, kPhoneXTabBarHeight);
}
else{
self.tabBar.frame = CGRectMake(0, self.view.frame.size.height - kTabBarHeight, self.view.frame.size.width, kTabBarHeight);
}
I encountered this today with a a UITabBar manually added to the view controller in the storyboard, when aligning to the bottom of the safe area with a constant of 0 I would get the issue but changing it to 1 would fix the problem. Having the UITabBar up 1 pixel more than normal was acceptable for my application.
I have scratched my head over this problem. It seems to be associated with how the tabBar is initialized and added to view hierarchy. I also tried above solutions like calling invalidateIntrinsicContentSize, setting the frame, and also bottomInsets inside a UITabBar subclass. They seem to work however temporarily and they break of some other scenario or regress the tab bar by causing some ambiguous layout issue. When I was debugging the issue I tried assigning the height constraints to the UITabBar and centerYAnchor, however neither fixed the problem. I realized in view debugger that the tabBar height was correct before and after the problem reproduced, which led me to think that the problem was in the subviews. I used the below code to successfully fix this problem without regressing any other scenario.
- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
if (DEVICE_IS_IPHONEX())
{
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
for (UIView *view in self.tabBar.subviews)
{
if ([NSStringFromClass(view.class) containsString:#"UITabBarButton"])
{
if (#available (iOS 11, *))
{
[view.bottomAnchor constraintEqualToAnchor:view.superview.safeAreaLayoutGuide.bottomAnchor].active = YES;
}
}
}
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self.tabBar layoutSubviews];
}];
}
}
Assumptions: I am doing this only for iPhone X, since it doesn't seem to reproduce on any other device at the moment.
Is based on the assumption that Apple doesn't change the name of the UITabBarButton class in future iOS releases.
We're doing this on UITabBarButton only when means if apple adds more internal subviews in to UITabBar we might need to modify the code to adjust for that.
Please lemme know if this works, will be open to suggestions and improvements!
It should be simple to create a swift equivalent for this.
From this tutorial:
https://github.com/eggswift/ESTabBarController
and after initialization of tab bar writing this line in appdelegate class
(self.tabBarController.tabBar as? ESTabBar)?.itemCustomPositioning = .fillIncludeSeparator
Solves my problem of tab bar.
Hope its solves your problem
Thanks
Select tabbar and set "Save Area Relative Margins" checkbox in Inspector Editor like this:
I had the similar problem, at first it was rendered correctly but after setting up badgeValue on one of the tabBarItem it broke the layout.
What it worked for me without subclassing UITabBar was this, on my already created UITabBarController subclass.
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLayoutYAxisAnchor *tabBarBottomAnchor = self.tabBar.bottomAnchor;
NSLayoutYAxisAnchor *tabBarSuperviewBottomAnchor = self.tabBar.superview.bottomAnchor;
[tabBarBottomAnchor constraintEqualToAnchor:tabBarSuperviewBottomAnchor].active = YES;
[self.view layoutIfNeeded];
}
I used tabBar superview to make sure that the constraints/anchors are on the same view hierarchy and avoid crashes.
Based on my understanding, since this seems to be a UIKit bug, we just need to rewrite/re-set the tab bar constraints so the auto layout engine can layout the tab bar again correctly.
If you have any height constraint for the Tab Bar try removing it .
Faced the same problem and removing this solved the issue.
I created new UITabBarController in my storyboard and pushed all view controllers to this new UITabBarConttoller. So, all work well in iPhone X simulator.
For iPhone you can do this, Subclass UITabBarController.
class MyTabBarController: UITabBarController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11, *) {
self.tabBar.heightAnchor.constraint(equalToConstant: 40).isActive = true
self.tabBar.invalidateIntrinsicContentSize()
}
}
}
Goto Storyboard and allow Use Safe Area Layout Guide and change class of UITabbarController to MyTabBarController
P.S This solution is not tested in case of universal application and iPad.
try to change splash screen with #3x size is (3726 × 6624)
For me, remove [self.tabBar setBackgroundImage:] work, maybe it's UIKit bug
For me this fixed all the issues:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let currentHeight = tabBar.frame.height
tabBar.frame = CGRect(x: 0, y: view.frame.size.height - currentHeight, width: view.frame.size.width, height: currentHeight)
}
I was using a UITabBarController in the storyboard and at first it was working alright for me, but after upgrading to newer Xcode version it started giving me issues related to the height of the tabBar.
For me, the fix was to delete the existing UITabBarController from storyboard and re-create by dragging it from the interface builder objects library.
For those who write whole UITabBarController programmatically, you can use UITabBarItem.appearance().titlePositionAdjustment to adjust the title position
So in this case that you want add a gap between Icon and Title use it in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
// Specify amount to offset a position, positive for right or down, negative for left or up
let verticalUIOffset = UIOffset(horizontal: 0, vertical: hasTopNotch() ? 5 : 0)
UITabBarItem.appearance().titlePositionAdjustment = verticalUIOffset
}
detecting if device has Notch screen:
func hasTopNotch() -> Bool {
if #available(iOS 11.0, tvOS 11.0, *) {
return UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0 > 20
}
return false
}
For me the solution was to select the Tab Bar in the view hierarchy, then go to:
Editor -> Resolve Auto Layout Issues, and under "Selected Views" (not "All views in view") choose "Add missing constraints".
I was having the same issue which was solved by setting the items of the tabBar after the tab bar was laid out.
In my case the issue happened when:
There is a custom view controller
A UITabBar is created in the initializer of the view controller
The tab bar items are set before view did load
In view did load the tab bar is added to the main view of the view controller
Then, the items are rendered as you mention.
I think this is a bug UIKit from iPhoneX.
because it works:
if (#available(iOS 11.0, *)) {
if ([UIApplication sharedApplication].keyWindow.safeAreaInsets.top > 0.0) {
self.tabBarBottomLayoutConstraint.constant = 1.0;
}
}
I'm developing my app to work with iOS7.
I have a UINavigationController I'm pushing a UIViewController that has a ScrollView inside it. Inside the scrollView I have a tableView.
Is it possible to achieve that when I scroll the tableView inside the scrollView the list will appear behind that Status bar. Same why it would be if I had a UINavigationController and a UIViewController with a tableView in it.
So this it the hierarchy :
UINavigationController -> UIViewController -> UIScrollView -> UITableView .
and I want that when a user scroll the table,the cells in the top will be visible under the status bar.
If there is no UIScrollView it happens automatically in iOS7.
Thanks.
Just set automaticallyAdjustsScrollViewInsets to NO in the viewController init method.
In Storyboard, you can switch the property directly in the property panel when the UIViewController is selected.
If you use xib, just set it like this:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self.automaticallyAdjustsScrollViewInsets = NO;
}
Note: this is right since iOS7 and still in iOS8.
none of the above workd for me, until I noticed that I had to set Content Insets from Automatically to Never in the interfacebuilder:
Starting with iOS 11 you can use this new property with a fallback (Swift 4):
if #available(iOS 11.0, *) {
scrollView.contentInsetAdjustmentBehavior = .never
} else {
self.automaticallyAdjustsScrollViewInsets = false
}
The answer from Skoua might work in some situations, but does have certain side-effects on iOS11 and later. Most notably, the scroll view will start propagating safe areas to its children, which can mess up your layout while scrolling if you use the safe areas or layout margins.
Apple explains this very well and in detail in this WWDC session and also mentions explicitly that contentInsetAdjustmentBehavior = .never can have side-effects and is not what you should use in most cases.
To get a scroll view that does not mess up our layout, but shows its content below the status bar (or navigation bar), you should observe the safe area of your scroll view and adjust your custom content insets accordingly:
private var scrollViewSafeAreaObserver: NSKeyValueObservation!
override func viewDidLoad() {
...
if #available(iOS 11.0, *) {
self.scrollViewSafeAreaObserver = self.scrollView.observe(\.safeAreaInsets) { [weak self] (_, _) in
self?.scrollViewSafeAreaInsetsDidChange()
}
} else {
self.automaticallyAdjustsScrollViewInsets = false
}
}
#available(iOS 11.0, *)
func scrollViewSafeAreaInsetsDidChange() {
self.scrollView.contentInset.top = -self.scrollView.safeAreaInsets.top
}
deinit {
self.scrollViewSafeAreaObserver?.invalidate()
self.scrollViewSafeAreaObserver = nil
}
Why does this work? Because we leave contentInsetAdjustmentBehavior = .automatic. This will give us normal behaviour when it comes to scrolling and non-scrolling axis, but the UIScrollView will also "convert" any safe areas to content insets. Since we don't want this for our top edge, we simply set the negative top safe area as our custom insets, which will counter any insets set by the scroll view.
Thats just dumb from Apple. One more weird behaviour to worry about. Anyhow, I ended up setting the scroll view content inset for top to -20 (from IB).
I found the solution! Just set:
self.automaticallyAdjustsScrollViewInsets = false
on the view controller that has the UIScrollView.
You probably has seen this recommendation a thousand times but, check the 'The Status Bar' section on the iOS 7 transition guide(can't post the direct link here).
Blunt resuming, on ios7 the status bar is part of the view. This means that almost anything you put on your view, will be under the bar, unless you say the contrary. A work around i found for that problem was making the status bar opaque.
Another way could be disabling the status bar on that specific view. Like on this thread.
I have had a similar sort of problem, and I found that to ensure my scrollview content doesn't go under the status bar, I applied a setContentInset.
So in your situation, if you are using an inset, I would use suggest using UIScrollViewDelegate or scrollViewDidScroll tailored to your tableview. If a scroll is occurring, disregard the scrollview inset.
don't hide the statusBar, the scrollView won't jump
In Cocoa-Touch we find a control UISearchBar. I need to customize it so that the search icon (which we click performs the respective action) present in the textfield is right aligned. Normally we find it left aligned. Is it possible to do so? I have done R & D but couldn't find it ...
How can I do it? Are there any good tutorials where we can find it?
Unfortunately the UISearchBar wasn't designed to directly support this. In accomplishing what we want it's likely that we're going to have to use workarounds that compromise visually or functionality wise or write a lot of customising code.
I've outlined a couple of approaches that get close to what we want and may be of use.
Accessing Subviews Approach
The text within a UISearchBar is a UITextField that is a subview of that UISearchBar and is accessible through the usually means i.e. view.subviews.
The magnifying glass search icon is a UIImageView in the leftView property of the UITextField. We can switch it over to the right using the following code:
// The text within a UISearchView is a UITextField that is a subview of that UISearchView.
UITextField *searchField;
for (UIView *subview in self.searchBar.subviews)
{
if ([subview isKindOfClass:[UITextField class]]) {
searchField = (UITextField *)subview;
break;
}
}
// The icon is accessible through the 'leftView' property of the UITextField.
// We set it to the 'rightView' instead.
if (searchField)
{
UIView *searchIcon = searchField.leftView;
if ([searchIcon isKindOfClass:[UIImageView class]]) {
NSLog(#"aye");
}
searchField.rightView = searchIcon;
searchField.leftViewMode = UITextFieldViewModeNever;
searchField.rightViewMode = UITextFieldViewModeAlways;
}
Which gives us the following result:
As you can see the icon overruns the edge of the rounded rectangle and the clear icon has disappeared. In addition, the rightView property was intended for other content such as the search results button and bookmarks button. Putting the search icon here may conflict with this functionality in some way even if you were able to use padding of offsets to position it appropriately.
In the very least you can use this approach to extract the icon as a UIImageView and position it explicitly where you see fit as you would any other view and write custom code to handle tap events etc.
Customizing Appearance Approach
In iOS v5.0 and later, you can customize the appearance of search bars using the methods listed here. The following code makes use of offsets to position the icon and text within the UISearchBar:
[self.searchBar setPositionAdjustment:UIOffsetMake(255, 0) forSearchBarIcon:UISearchBarIconSearch];
[self.searchBar setSearchTextPositionAdjustment:UIOffsetMake(-270, 0)];
On a standard UISearchBar of 320 width it looks like the following:
This would keep all event functionality associated with the icon working as expected but unfortunately the UITextField is confused and despite its width, it only displays a very small portion of its text. If you could find a way to allow the text to display beyond what it believes to be the end of the UITextField, this will probably be your best bet.
My requirement was that the search icon should be on the right, and that it should hide whenever the X icon showed up.
I came up with a similar solution as above, but a bit different and also using Swift 2 and AutoLayout. The basic idea is to hide the left view, create a new view with the magnifying glass image, and use autolayout to pin it to the right. Then use UISearchBarDelegate methods to hide the search icon whenever the searchbar text is not empty.
class CustomSearchView: UIView {
//the magnifying glass we will put on the right
lazy var magnifyingGlass = UIImageView()
#IBOutlet weak var searchBar: UISearchBar!
func commonInit() {
searchBar.delegate = self
searchBar.becomeFirstResponder()
//find the search bar object inside the subview stack
if let searchField = self.searchBar.subviews.firstObject?.subviews.filter(
{($0 as? UITextField) != nil }).firstObject as? UITextField {
//hides the left magnifying glass icon
searchField.leftViewMode = .Never
//add a magnifying glass image to the right image view. this can be anything, but we are just using the system default
//from the old left view
magnifyingGlass.image = (searchField.leftView! as? UIImageView)?.image
//add the new view to the stack
searchField.addSubview(magnifyingGlass)
//use autolayout constraints to pin the view to the right
let views = ["view": magnifyingGlass]
magnifyingGlass.translatesAutoresizingMaskIntoConstraints = false
searchField.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
"H:[view(12)]-|", options: [], metrics: nil, views: views))
searchField.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
"V:[view]-|", options: [], metrics: nil, views: views))
}
}
//hides whenever text is not empty
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
magnifyingGlass.hidden = searchText != ""
}
}