changing the height of UITabBar in iOS7/8? - uitabbarcontroller

I am trying to change the height of the stock UITabBar to 44px, similar to Tweetbot's tab bar height. I've also seen a few other apps do this as well.
however, when i try to set the height it still remains the same
self.tabBar.frame.height = 40
are we not allowed to change the tab bar height? and if so what is a good alternative? using a toolbar?

It seems everybody says this can't be done easily
In your storyboard give your UITabBar a custom subclass name, then implement the subclass with the following
This tells all views that use the tab bar that it should be a certain height.
#implementation MyTabBar
-(CGSize)sizeThatFits:(CGSize)size
{
CGSize sizeThatFits = [super sizeThatFits:size];
sizeThatFits.height = 100;
return sizeThatFits;
}
#end

SomeGuy's answer above worked for me. Here's the Swift translation for anyone who may need it. I made the height close to what it seems most popular apps use.
class TabBar: UITabBar {
override func sizeThatFits(size: CGSize) -> CGSize {
var sizeThatFits = super.sizeThatFits(size)
sizeThatFits.height = 38
return sizeThatFits
}
}

For Swift 3 and xcode 8
extension UITabBar {
override open func sizeThatFits(_ size: CGSize) -> CGSize {
var sizeThatFits = super.sizeThatFits(size)
sizeThatFits.height = 80 // adjust your size here
return sizeThatFits
}
}

In your UITabBarController
- (void)viewWillLayoutSubviews {
CGRect tabFrame = self.tabBar.frame;
tabFrame.size.height = 80;
tabFrame.origin.y = self.view.frame.size.height - 80;
self.tabBar.frame = tabFrame;
}

In swift it is even simpler than all solutions suggested above by using an extensions to UITabBar, no subclassing necessary:
extension UITabBar {
override public func sizeThatFits(size: CGSize) -> CGSize {
super.sizeThatFits(size)
var sizeThatFits = super.sizeThatFits(size)
sizeThatFits.height = <Insert your height here>
return sizeThatFits
}
}

If you have auto layout enabled, you will need to override instrinsicContentSize instead
Proper usage of intrinsicContentSize and sizeThatFits: on UIView Subclass with autolayout
class TabBar: UITabBar {
override func intrinsicContentSize() -> CGSize {
var intrinsicSize = super.frame.size
intrinsicSize.height = 120
return intrinsicSize
}
}

The developer doesn't own the tabBar, the framework does. It will fight you to make sure that the tabBar stays the same height. If you want to work around this, you can make your own toolbar and add autlayout constraints to its height to force it to stay whatever height you'd like.

If you are on iOS 11 then following will help
-(CGSize)sizeThatFits:(CGSize)size
{
CGSize sizeThatFits = [super sizeThatFits:size];
sizeThatFits.height = 60;
if (#available(iOS 11.0, *)) {
UIWindow *window = UIApplication.sharedApplication.keyWindow;
CGFloat bottomPadding = window.safeAreaInsets.bottom;
sizeThatFits.height += bottomPadding;
}
return sizeThatFits;
}
Basically need to cover safe area, else tabbar height on iPhone X appears to be low.

For iOS11 and above you can use below:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (#available(iOS 11.0, *)) {
self.additionalSafeAreaInsets = UIEdgeInsetsMake(0, 0, 20, 0);
}
}
And for all os, create UITabBar subclass, use it in the UITabBarController and implement below method in the implementation of custom tab bar class:
-(CGSize)sizeThatFits:(CGSize)size {
CGSize sizeThatFits = [super sizeThatFits:size];
sizeThatFits.height = kBarHeight;
return sizeThatFits;
}

Related

Opaque background Toolbar when scrolling

I have opaque the background of the TabBar but not the ToolBar when I expected both...
.onAppear{
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithOpaqueBackground()
UITabBar.appearance().standardAppearance = tabBarAppearance
let toolBarApperance = UIToolbarAppearance()
toolBarApperance.configureWithOpaqueBackground()
UIToolbar.appearance().standardAppearance = toolBarApperance
UIToolbar.appearance().scrollEdgeAppearance = toolBarApperance
}
Some hint plis ;)
toolbarBackground(_:for:) solves the problem but only in iOS 16
if #available(iOS 16.0, *) {
...
.toolbarBackground(Color.black, for: .automatic)
}

How to change background color for tab in tvOS 13?

TvOS 13. I have a UITabBarController with tabs. And can customize almost everything except this obvious thing: focused tab's background. It's always white.
Guide tells
Specify tints for selected and unselected items
I tried:
view.backgroundColor = .purple
tabBar.tintColor = .yellow
tabBar.barTintColor = .red
tabBar.unselectedItemTintColor = .brown
tabBar.backgroundColor = .green
tabBar.backgroundImage = UIColor.blue.toImage()
tabBar.shadowImage = UIColor.orange.toImage()
tabBar.selectionIndicatorImage = UIColor.burgundy.toImage()
Nothing helped.
After playing a bit with various properties of UITabBar and UITabBarController, I finally figured it out.
The property to change focused items background color is selectionIndicatorTintColor of UITabBarAppearance (documentation).
Since it is available on tvOS >= 13.0, you will have to wrap the assignment like this:
if #available(tvOS 13.0, *) {
tabBar.standardAppearance.selectionIndicatorTintColor = .white
}
For #davidv and other folks, here is my solution:
extension UIView {
func subviews<T:UIView>(ofType type: T.Type) -> [T] {
var result = self.subviews.compactMap { $0 as? T }
for sub in self.subviews {
result.append(contentsOf: sub.subviews(ofType: type))
}
return result
}
}
extension UIViewController {
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
// перекраска кнопки
let allSubviews = tabBar.subviews(ofType: UIView.self)
let whiteSubviews = allSubviews.filter { $0.backgroundColor == .white }
for s in whiteSubviews {
s.backgroundColor = .gold
}
}
}
UPDATE:
For coloring text:
item.setTitleTextAttributes([NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: colorSelected], for: [.focused])
item.setTitleTextAttributes([NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: colorSelected], for: [.highlighted])
item.setTitleTextAttributes([NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: colorUnselected], for: [.normal])
For coloring background:
tabBar.standardAppearance.selectionIndicatorTintColor = .gold
I accomplish this through a UITabBar extension. The view that is displayed on focus contains a UIMotionEffect so we check against that to find it.
#available(tvOS 13.0, *)
extension UITabBar {
var focusBackgroundView: UIView? {
let allSubviews: [UIView] = subviews.flatMap { [$0] + $0.subviews as [UIView] }
return allSubviews.first{ !$0.motionEffects.isEmpty }
}
}
Usage:
myTabBar.focusBackgroundView.backgroundColor = .red

Animating individual characters in a SKLabelNode

Is there a more efficient way to animate text shivering with typewriting all in one sklabelnode? I'm trying to achieve the effect in some games like undertale where the words appear type writer style while they are shivering at the same time.
So far I've only been able to achieve it but with such luck:
class TextEffectScene: SKScene {
var typeWriterLabel : SKLabelNode?
var shiveringText_L : SKLabelNode?
var shiveringText_O : SKLabelNode?
var shiveringText_S : SKLabelNode?
var shiveringText_E : SKLabelNode?
var shiveringText_R : SKLabelNode?
var button : SKSpriteNode?
override func sceneDidLoad() {
button = self.childNode(withName: "//button") as? SKSpriteNode
self.scaleMode = .aspectFill //Very important for ensuring that the screen sizes do not change after transitioning to other scenes
typeWriterLabel = self.childNode(withName: "//typeWriterLabel") as? SKLabelNode
shiveringText_L = self.childNode(withName: "//L") as? SKLabelNode
shiveringText_O = self.childNode(withName: "//O") as? SKLabelNode
shiveringText_S = self.childNode(withName: "//S") as? SKLabelNode
shiveringText_E = self.childNode(withName: "//E") as? SKLabelNode
shiveringText_R = self.childNode(withName: "//R") as? SKLabelNode
}
// Type writer style animation
override func didMove(to view: SKView) {
fireTyping()
shiveringText_L?.run(SKAction.repeatForever(SKAction.init(named: "shivering")!))
shiveringText_O?.run(SKAction.repeatForever(SKAction.init(named: "shivering2")!))
shiveringText_S?.run(SKAction.repeatForever(SKAction.init(named: "shivering3")!))
shiveringText_E?.run(SKAction.repeatForever(SKAction.init(named: "shivering4")!))
shiveringText_R?.run(SKAction.repeatForever(SKAction.init(named: "shivering5")!))
}
let myText = Array("You just lost the game :)".characters)
var myCounter = 0
var timer:Timer?
func fireTyping(){
typeWriterLabel?.text = ""
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(TextEffectScene.typeLetter), userInfo: nil, repeats: true)
}
func typeLetter(){
if myCounter < myText.count {
typeWriterLabel?.text = (typeWriterLabel?.text!)! + String(myText[myCounter])
//let randomInterval = Double((arc4random_uniform(8)+1))/20 Random typing speed
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(TextEffectScene.typeLetter), userInfo: nil, repeats: false)
} else {
timer?.invalidate() // stop the timer
}
myCounter += 1
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let location = touch?.location(in: self) {
if (button?.contains(location))! {
print("doggoSceneLoaded")
let transition = SKTransition.fade(withDuration: 0.5)
let newScene = SKScene(fileNamed: "GameScene") as! GameScene
self.view?.presentScene(newScene, transition: transition)
}
}
}
}
As you can see, I had to animate each individual label node in a word "loser".
To create this effect:
For those who may be interested to Swift 4 I've realized a gitHub project around this special request called SKAdvancedLabelNode.
You can find here all sources.
Usage:
// horizontal alignment : left
var advLabel = SKAdvancedLabelNode(fontNamed:"Optima-ExtraBlack")
advLabel.name = "advLabel"
advLabel.text = labelTxt
advLabel.fontSize = 20.0
advLabel.fontColor = .green
advLabel.horizontalAlignmentMode = .left
addChild(self.advLabel)
advLabel.position = CGPoint(x:frame.width / 2.5, y:frame.height*0.70)
advLabel.sequentiallyBouncingZoom(delay: 0.3,infinite: true)
Output:
something i have a lot of experience with... There is no way to do this properly outside of what you are already doing. My solution (for a text game) was to use NSAttributedString alongside CoreAnimation which allows you to have crazy good animations over UILabels... Then adding the UILabels in over top of SpriteKit.
I was working on a better SKLabel subclass, but ultimately gave up on it after I realized that there was no way to get the kerning right without a lot more work.
It is possible to use an SKSpriteNode and have a view as a texture, then you would just update the texture every frame, but this requires even more timing / resources.
The best way to do this is in the SK Editor how you have been doing it. If you need a lot of animated text, then you need to use UIKit and NSAttributedString alongside CoreAnimation for fancy things.
This is a huge, massive oversight IMO and is a considerable drawback to SpriteKit. SKLabelNode SUCKS.
As I said in a comment, you can subclass from an SKNode and use it to generate your labels for each characters. You then store the labels in an array for future reference.
I've thrown something together quickly and it works pretty well. I had to play a little bit with positionning so it looks decent, because spaces were a bit too small. Also horizontal alignement of each label has to be .left or else, it will be all crooked.
Anyway, it'S super easy to use! Go give it a try!
Here is a link to the gist I just created.
https://gist.github.com/sonoblaise/e3e1c04b57940a37bb9e6d9929ccce27

Wrong margin between searchbar and navigation bar

I don't know why this happens. I've a UIViewController with a UITableView where i would like to add a search bar for querying my server using the new UISearchController (Swift 3, Xcode 8.1).
I've tried everything like add the search bar in Interface builder, set search bar background to UIImage but nothing works.
This is the view controller's class barebone:
class MyViewController: UIViewController {
var searchController = UISearchController(searchResultsController: nil)
fileprivate func setupSearchController() {
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.barTintColor = .myColor()
searchController.searchBar.tintColor = .white
searchController.searchBar.backgroundColor = .myColor()
searchController.searchBar.placeholder = "searchSomething"
searchController.searchBar.delegate = self
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
}
}
extension MyViewController: UISearchResultsUpdating, UISearchBarDelegate {
// delegates methods...
}
Maybe it's something regarding my design pattern but i'm not really sure as UISearchController is really new and i haven't found any good examples on the web regarding its use.
You can set a border around the search bar the same red color as the nav.
searchBar.layer.borderColor = (your red color).cgColor
searchBar.layer.borderWidth = 1

iOS8 TabbarController inside a UISplitviewController Master

I've tried to expand the default Apple MasterDetail Template by adding a UITabbarController in front of the UINavigationController of the MasterView, so there is a structure like this:
UISplitViewController (Master) > UITabbarController > UINavigationController > UITableViewController
But if I run the App, after changing application(didFinishLaunchingWithOptions) to use the correct ViewController, and try to perform the ShowDetails Segue the DetailsView ist presented Modally on the iPhone. On the other side the iPad Version is working as expected.
What am I forgot to do? Or how can I fix it?
I figured out how to put the detail on to the master's UINavigationController instead of presenting it modally over the UITabBarController.
Using the UISplitViewControllerDelegate method
- splitViewController:showDetailViewController:sender:
In case the UISplitViewController is collapsed get the masters navigation controller and push the detail view onto this navigation controller:
- (BOOL)splitViewController:(UISplitViewController *)splitViewController
showDetailViewController:(UIViewController *)vc
sender:(id)sender {
NSLog(#"UISplitViewController collapsed: %d", splitViewController.collapsed);
// TODO: add introspection
if (splitViewController.collapsed) {
UITabBarController *master = (UITabBarController *) splitViewController.viewControllers[0];
UINavigationController *masterNavigationController = (UINavigationController *)master.selectedViewController;
// push detail view on the navigation controller
[masterNavigationController pushViewController:vc animated:YES];
return YES;
}
return NO;
}
Just to update the answers above. Since you can't push navigation controllers anymore, you have to push its top view controller instead.
func splitViewController(splitViewController: UISplitViewController, showDetailViewController vc: UIViewController, sender: AnyObject?) -> Bool {
if splitViewController.collapsed {
let tabBarController = splitViewController.viewControllers.first as! UITabBarController
let selectedNavigationViewController = tabBarController.selectedViewController as! UINavigationController
// Push view controller
var viewControllerToPush = vc
if let navController = vc as? UINavigationController {
viewControllerToPush = navController.topViewController
}
selectedNavigationViewController.pushViewController(viewControllerToPush, animated: true)
return true
}
return false
}
Here's my solution. Place in MasterViewController.m and remember to give your detail view a Storyboard ID in IB. In my case 'detail'.
-(BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([identifier isEqualToString:#"showDetail"] && self.splitViewController.collapsed) {
DetailViewController *myController = (DetailViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"detail"];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[myController setDetailItem:object];
[self.navigationController showViewController:myController sender:self];
return NO;
}
return YES;
}
There is another way to do it without code.
After you embedded the the UINavigationController in the TabBarController embed the TabBarController in another UINavigationController. So you will have: SplitViewController -> Master -> NavCon -> TabBar -> NavCon -> TableViewController.
It's much easier doing like this, but there a bug that I haven't found out how to fix. The navigation bar presented will be that of the TabBarController, not the TableViewController. Any ideas how to fix that?
Subclass TabBarController like this:
- (void)showViewController:(UIViewController *)vc sender:(id)sender
{
if ([self.selectedViewController isKindOfClass:UINavigationController.class])
[self.selectedViewController showViewController:vc sender:sender];
else
[super showViewController:vc sender:sender];
}
- (UIViewController*)separateSecondaryViewControllerForSplitViewController:(UISplitViewController *)splitViewController
{
return [self.selectedViewController separateSecondaryViewControllerForSplitViewController:splitViewController];
}
- (void)collapseSecondaryViewController:(UIViewController *)secondaryViewController forSplitViewController:(UISplitViewController *)splitViewController
{
[self.selectedViewController.navigationController collapseSecondaryViewController:secondaryViewController forSplitViewController:splitViewController];
}
See this question for complete explanation.
Here is an alternative that is based on testing the size classes of the splitViewController :
Use a custom UISplitViewController (subclass)
Override the showDetailViewController operation
Use the traitCollection to determine the class of the UISplitViewController
If the horizontal class is Compact, get the navigationController to call showViewController
Here is the the code of the custom UISplitViewController :
import UIKit
class CustomSplitViewController: UISplitViewController {
override func showDetailViewController(vc: UIViewController!, sender: AnyObject!) {
if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClass.Compact) {
if let tabBarController = self.viewControllers[0] as? UITabBarController {
if let navigationController = tabBarController.selectedViewController as? UINavigationController {
navigationController.showViewController(vc, sender: sender)
return
}
}
}
super.showDetailViewController(vc, sender: sender)
}
}
Do not forget to the set the custom class in the storyboard.
Tested in the simulator of iPhone 6, iPhone 6+ and iPad Air and worked as expected.