Add subview in UITabBar - ios

I want to customise UITabBar by subclassing it I am not able to get the UITabBar frame here is my code
in home_tabbar.h
#import <UIKit/UIKit.h>
#interface home_tabbar : UITabBar
-(void) changeFrame;
#end
in home_tabbar.m
#import "home_tabbar.h"
#implementation home_tabbar
- (void)viewDidAppear:(BOOL)animated {
[self changeFrame];
}
- (void)changeFrame
{
CGRect frame = self.viewForLastBaselineLayout.frame;
NSLog(#"Frame x= %f y=%f width=%f height=%f",frame.origin.x,frame.origin.y,frame.size.width,frame.size.height);
}
#end

You can do it like this…
Subclass UITabBarController, and add a property for the view you want to add, a button in this case…
#property (strong, nonatomic) IBOutlet UIButton *videoButton;
Configure in viewDidLoad…
- (void)viewDidLoad
{
[super viewDidLoad];
self.videoButton.layer.cornerRadius = 4.0;
self.videoButton.backgroundColor = [UIColor yellowColor];
self.videoButton.clipsToBounds = YES;
[self.tabBar addSubview: self.videoButton];
}
then in viewDidLayoutSubviews…
- (void) viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
// Only need to do this once if the orientation is fixed
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self.videoButton.center = (CGPoint){CGRectGetMidX(self.tabBar.bounds), CGRectGetMidY(self.tabBar.bounds)};
});
// system moves subviews behind tab bar buttons, this fixes
[self.tabBar bringSubviewToFront: self.videoButton];
}

I have created a class to customize the UITabBar. In my case I had to add a background image and update the height.
Here my code:
class DDTabBar: UITabBar {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let backgroundImage = UIImageView(image: UIImage(named: "TabBar_Background"))
backgroundImage.contentMode = UIViewContentMode.scaleAspectFill
var tabFrame = self.bounds
tabFrame.size.height = 75
backgroundImage.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tabFrame.origin.y = self.bounds.size.height - 75
backgroundImage.frame = tabFrame
self.insertSubview(backgroundImage, at: 0)
}
}

Related

iOS: the background color of the header of my TableView is not changing anymore in iOS13

My TableView's header is not displaying well in iOS13. No matter what color I put, it always displays a light gray now...
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{ //Section color & style
UITableViewHeaderFooterView *v = (UITableViewHeaderFooterView *)view;
v.backgroundView.alpha = 1;
v.textLabel.textColor = sectionColor;
v.textLabel.font = sectionFont;
v.textLabel.numberOfLines = 1;
v.textLabel.minimumScaleFactor = 0.5;
v.textLabel.adjustsFontSizeToFitWidth = YES;
v.backgroundView.backgroundColor = [UIColor blueColor];
}
iOS12:
iOS13:
It is strange because when I put a stop in the debugger in step by step, it displays me the good image in iOS13, but not in the app:
Any suggestions, thanks in advance ?
I was noticing the same thing in one of my apps.
Then saw a log message in the console:
Setting the background color on UITableViewHeaderFooterView has been
deprecated. Please set a custom UIView with your desired background
color to the backgroundView property instead.
Setting a custom UIView with the desired background color as the backgroundView of the UITableViewHeaderFooterView solved the problem.
Code sample
class SomeHeaderView: UITableViewHeaderFooterView {
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
configure()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configure() {
let backgroundView = UIView(frame: .zero)
backgroundView.backgroundColor = .blue
self.backgroundView = backgroundView
}
}
This works for me.
v.contentView.backgroundColor = .blue
instead of
v.backgroundView.backgroundColor = .blue
Try to add overlay view and change this color for this view.
UIView *coloredView = [[UIView alloc] init];
coloredView.backgroundColor = [UIColor blueColor];
[v addSubview:coloredView];
[[coloredView.leadingAnchor constraintEqualToAnchor:v.leadingAnchor constant:0] setActive:YES];
[[coloredView.trailingAnchor constraintEqualToAnchor:v.trailingAnchor constant:0] setActive:YES];
[[coloredView.topAnchor constraintEqualToAnchor:v.topAnchor constant:0] setActive:YES];
[[coloredView.bottomAnchor constraintEqualToAnchor:v.bottomAnchor constant:0] setActive:YES];

Why page Push animation Tabbar moving up in the iPhone X

I build a app Demo, use hidesBottomBarWhenPushed hide Tabbar in Push Animation.
But, When I click Jump Button Tabbar move up!? like this:
Answer provided by VoidLess fixes TabBar problems only partially. It fixes layout problems within tabbar, but if you use viewcontroller that hides 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 tabbar being rendered out of place). See the code bellow that fixes both of the 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
This is my way。 Declare a subclass of UITabBar, such as ActionTabBar
swift 3,4
class ActionTabBar: UITabBar {
override var frame: CGRect {
get {
return super.frame
}
set {
var tmp = newValue
if let superview = self.superview, tmp.maxY != superview.frame.height {
tmp.origin.y = superview.frame.height - tmp.height
}
super.frame = tmp
}
}
}
Objective-C
#implementation ActionTabbar
- (void)setFrame:(CGRect)frame
{
if (self.superview && CGRectGetMaxY(self.superview.bounds) != CGRectGetMaxY(frame)) {
frame.origin.y = CGRectGetHeight(self.superview.bounds) - CGRectGetHeight(frame);
}
[super setFrame:frame];
}
#end
Declare a subclass of NavigationController
#implementation XXNavigationController
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
[super pushViewController:viewController animated:animated];
CGRect frame = self.tabBarController.tabBar.frame;
frame.origin.y = [UIScreen mainScreen].bounds.size.height - frame.size.height;
self.tabBarController.tabBar.frame = frame;
}
Edit: After the release of 12.1.1, this issue has been fixed.
You can keep the original structure.
If you change your structure from
UITabBarController -> UINavigationController -> UIViewController
to
UINavigationController -> UITabBarController -> UIViewController
you will find this issue has been resolved. I really don't know why Apple doesn't fix this issue.
In iOS 12.1, this problem becomes more serious. You can see the TabBar text jump above the TabBar every time, if you use gesture to pop back.
Note: This way can definitely solve this problem, but I am not sure whether it's a good idea. Also, if your structure is quite complicated, you need to change lots of stuff.
i'll provide another solution for this bug(seems apple made).
and the solution is not to forbid the tabbar move up , but to make the black area will not show when tabbar move up
the core thing is add a subview to your viewcontroller as it deepest subview and this subview's frame is the window size.so when the tabbar moves up , this subview will shown insteadof black area
if (#available(iOS 11.0, *)) {
UIView* bgView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
bgView.autoresizingMask = UIViewAutoresizingNone;
bgView.backgroundColor = UIColorFromRGB(0xefeff4);
[self.view addSubview:bgView];
}
the convenience of this method is you will not have to subclass tabbar or overwrite navigationcontroller's push method
this problem seems fixed in iOS 11.2
Just need to add a launch image specifically for iPhone X to asset catalog (because it uses #3x) of size 1125 x 2436.
I hope this will solve your problem.

Rotate BarButtonItem by 45 degrees (animated)

looks like animations are not my speciality :/
In my navigation bar I have a custom BarButtonItem, a plus, to add stuff to the list. I wanted to rotate the plus by 45 degrees so it becomes a X when it was pressed and works as a cancel button then.
I added a button as custom view to the BarButtonItem by doing this:
#IBOutlet weak var addQuestionaryButton: UIBarButtonItem!
{
didSet {
let icon = UIImage(named: "add")
let iconSize = CGRect(origin: CGPoint.zero, size: icon!.size)
let iconButton = UIButton(frame: iconSize)
iconButton.setBackgroundImage(icon, for: .normal)
addQuestionaryButton.customView = iconButton
iconButton.addTarget(self, action: #selector(QuestionaryListViewController.addClicked(_:)), for: .touchUpInside)
}
}
This seems to work fine.
Now if the button is pressed I do the following:
UIView.animate(withDuration: 0.5, animations:{
self.addQuestionaryButton.customView!.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_4))
})
I can see the button starting to rotate but somehow it gets totally deformed. See the pictures for that:
Before:
After:
I don't understand why this happens.
How do I correctly animate the BarButtonItem?
Thanks in advance.
Greetings
Don't know why this is happening(my guess is when doing the transform, the frame of the customView will be messed up) but found a solution.
Just put the button into another UIView and then set this UIView as the customView of the UIBarButtonItem.
When doing the animation, don't rotate the UIView, rotate the button.
Reminder: Don't forget to set the frame of this UIView.
Sample code in Objective C:
Init:
#interface ViewController ()
#property (nonatomic, strong) UIButton* itemButton;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//button init
self.itemButton = [[UIButton alloc] init];
[self.itemButton setBackgroundImage:[UIImage imageNamed:#"imgName"] forState:UIControlStateNormal];
self.itemButton.frame = CGRectMake(0, 0, 30, 30);
//container for the button
UIView* btnContainer = [[UIView alloc] init];
btnContainer.frame = CGRectMake(0, 0, 30, 30);
[btnContainer addSubview:self.itemButton];
//set the container as customView of the UIBarButtonItem
UIBarButtonItem* applyButton = [[UIBarButtonItem alloc] initWithCustomView:btnContainer];
self.navigationItem.rightBarButtonItem = applyButton;
}
Code to trigger the rotation:
[UIView animateWithDuration:0.5 animations:^{
self.itemButton.transform = CGAffineTransformRotate(self.itemButton.transform, M_PI_4);
}];

Hide UINavigationBar without losing Status Bar background

I'm working on an app where we use the white status bar tint and a dark background for the navigationBar. There is one scene where we want the navigationBar hidden but it also takes away the background color for the status bar. Is there a simple solution to keep a dark background up with hiding the navigationBar at the same time?
My code to hide the navigation bar is:
[self.navigationController setNavigationBarHidden:YES];
or in Swift:
self.navigationController?.navigationBarHidden = true
Assuming you are developing for iOS7+: The statusbar doesn' have any background color on its own. In fact, the reason you are seeing a dark background when the navigation bar is visible, is because it extends upwards underneath the statusbar. So if you want to keep the status bar background you can simply add a view with an appropriate background color to the current scene (viewController, window , etc.). Give it a frame of UIApplication.sharedApplication.statusBarFrame.
-- UPDATE 1 --
Sample code (Swift 3) Gives you a solid black status bar background:
class ViewController: UIViewController {
private let statusBarUnderlay = UIView()
override func viewDidLoad() {
super.viewDidLoad()
statusBarUnderlay.backgroundColor = UIColor.black
view.addSubview(statusBarUnderlay)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
statusBarUnderlay.frame = UIApplication.shared.statusBarFrame
}
}
-- UPDATE 2 --
While we're at it. The above code is not how you should lay out your views. Instead, subclass UIView and do your layout there. Then override loadView of your UIViewController subclass and return an instance of your custom view.
class View: UIView {
private let statusBarUnderlay = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
statusBarUnderlay.backgroundColor = UIColor.black
addSubview(statusBarUnderlay)
}
required init?(coder aDecoder: NSCoder) {
fatalError("Storyboards are incompatible with truth and beauty.")
}
override func layoutSubviews() {
super.layoutSubviews()
statusBarUnderlay.frame = UIApplication.shared.statusBarFrame
}
}
All I needed to do was add a secondary view and set it to below the status bar like this:
-(void)viewForStatusBar {
UIView *view = [UIView new];
[self.view addSubview:view];
view.backgroundColor = [UIColor blackColor];
view.frame = CGRectMake(0, -20, [UIScreen mainScreen].bounds.size.width, 20);
}
Or in Swift:
func viewForStatusBar() {
let view = UIView()
self.view.addSubview(view)
view.backgroundColor = .blackColor()
view.frame = CGRectMake(0, -20, UIScreen.mainScreen().bounds.size.width, 20)
}
Here's the implementation of LTNavigationBar
I think it may help you with.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat offsetY = scrollView.contentOffset.y;
if (offsetY > 0) {
if (offsetY >= 44) {
[self setNavigationBarTransformProgress:1];
} else {
[self setNavigationBarTransformProgress:(offsetY / 44)];
}
} else {
[self setNavigationBarTransformProgress:0];
self.navigationController.navigationBar.backIndicatorImage = [UIImage new];
}
}
- (void)setNavigationBarTransformProgress:(CGFloat)progress
{
[self.navigationController.navigationBar.transform = CGAffineTransformMakeTranslation:(0, -44 * progress)];
}
It makes the navigation bar hidden and status bar have the same background color as navigation bar when scrolling the view.If you don't need scrolling, you can just call [self setNavigationBarTransformProgress:1]

Auto-Resize UITableView Headers on Rotate (Mostly on iPad)

I feel like this is going to be a simple answer revolving around AutoResizingMasks, but I can't seem to wrap my head around this topic.
I've got an iPad app that shows 2 UITableViews side-by-side. When I rotate from Portrait to Landscape and back, the cells in the UITableView resize perfectly, on-the-fly, while the rotation is occurring. I'm using UITableViewCellStyleSubtitle UITableViewCells (not subclassed for now), and I've set the UITableView up in IB to anchor to the top, left and bottom edges (for the left UITableView) and to have a flexible width.
I'm supplying my own UIView object for
- (UIView *)tableView:(UITableView *)tableView
viewForHeaderInSection:(NSInteger)section
Here's what I've got so far (called as a class method from another class):
+ (UIView *)headerForTableView:(UITableView *)tv
{
// The view to return
UIView *headerView = [[UIView alloc]
initWithFrame:CGRectMake(0, 0, [tv frame].size.width, someHeight)];
[headerView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin];
// Other layout logic... doesn't seem to be the culprit
// Return the HeaderView
return headerView;
}
So, in either orientation, everything loads up just like I want. After rotation, if I manually call reloadData or wait until my app triggers it, or scroll the UITableView, the headerViews will resize and show themselves properly. What I can't figure out is how to get the AutoResizeMask property set properly so that the header will resize just like the cells.
Not a very good fix. But works :
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
[mTableView reloadData];
}
I faced the same issue recently. The trick was to use a custom view as the headerView of the table. Overriding layoutSubviews allowed me to control the layout at will. Below is an example.
#import "TableSectionHeader.h"
#implementation TableSectionHeader
- (id)initWithFrame:(CGRect)frame title:(NSString *)title
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
// Initialization code
headerLabel = [[UILabel alloc] initWithFrame:frame];
headerLabel.text = title;
headerLabel.textColor = [UIColor blackColor];
headerLabel.font = [UIFont boldSystemFontOfSize:17];
headerLabel.backgroundColor = [UIColor clearColor];
[self addSubview:headerLabel];
}
return self;
}
-(void)dealloc {
[headerLabel release];
[super dealloc];
}
-(void)layoutSubviews {
[super layoutSubviews];
NSInteger xOffset = ((55.0f / 768.0f) * self.bounds.size.width);
if (xOffset > 55.0f) {
xOffset = 55.0f;
}
headerLabel.frame = CGRectMake(xOffset, 15, self.bounds.size.width - xOffset * 2, 20);
}
+(UIView *) tableSectionHeaderWithText:(NSString *) text bounds:(CGRect)bounds {
TableSectionHeader *header = [[[TableSectionHeader alloc] initWithFrame:CGRectMake(0, 0, bounds.size.width, 40) title:text] autorelease];
return header;
}
+(CGFloat) tableSectionHeaderHeight {
return 40.0;
}
#end
I'd love to get a real answer to this, but for now, I've just re-worked my UITableView so that my "headers" are just cells inside the table. Resizing has no issues that way.
I have created UIView subclass where I've used visual constraints to stick the subview to the sides of the screen. And rotation is all fine.
class MLFlexibleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.segmentedControl)
self.setUpConstraints()
}
func setUpConstraints() {
let views = ["view" : self.segmentedControl]
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|-7-[view]-7-|", options: [], metrics: nil, views: views))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-15-[view]-15-|", options: [], metrics: nil, views: views))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let segmentedControl:UISegmentedControl = {
let segmentedControl = UISegmentedControl(items: ["Searches".localized, "Adverts".localized])
segmentedControl.selectedSegmentIndex = 0
segmentedControl.tintColor = UIColor.white
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
return segmentedControl
}()
}

Resources