I have a UIButton that is constrained to the view's safeAreaLayoutGuide bottom anchor and a UITabBar in that UIView. Everything is okay there. However, when I fullscreen an image, I hide the UITabBar. When I dismiss the fullscreen, I show the UITabBar again. However, the UIButton moves down and doesn't constrain to the UITabBar as it did before the UITabBar was hidden. The UIButton is covered partially by the UITabBar. Any solutions?
Here is the dismiss fullscreen code.
#objc func dismissFullscreenImage(_ sender: UITapGestureRecognizer) {
sender.view?.removeFromSuperview()
self.navigationController?.isNavigationBarHidden = false
self.tabBarController?.tabBar.isHidden = false
}
Here are two things you can try:
Solution 1:
Add this line to dismissFullscreenImage():
self.view.setNeedsLayout()
This invalidates the layout of self.view and causes it to be laid out again.
Solution 2:
You can avoid the need to relayout the view by making the navigationBar and tabBar invisible.
Instead of hiding/showing the navigationBar and tabBar by changing their isHidden properties, try setting their alpha values:
// hide
self.navigationController?.navigationBar.alpha = 0
self.tabBarController?.tabBar.alpha = 0
// show
self.navigationController?.navigationBar.alpha = 1
self.tabBarController?.tabBar.alpha = 1
Related
I'd like to implement a nav style like what is found in the "Add Reminder" view controller from Apple's Reminders (iOS 14). I've tried hooking into the scrollview delegate methods but I'm not sure how to change the alpha of the default nav bar background/shadow image.
I've tried changing the nav bar style on scroll and, while that works, it doesn't fade in/out like in the example. That makes me think the answer lies manipulating the alpha value. Thanks in advance!
I've found a (hacky) solution that works in iOS 14 (untested in other versions). It makes an assumption about the private view structure of UINavigationBar, so there's no guarantee that it will work in future iOS versions, but it's unlikely to crash - the worst that should happen is that the bar will fail to hide, or only partially hide.
Assuming that you are placing the code inside a UIViewController subclass that it acting as the delegate for a UITableView, UICollectionView or UIScrollView, the following should work:
override func viewDidLoad() {
super.viewDidLoad()
// this hides the bar initially
self.navigationController?.navigationBar.subviews.first?.alpha = 0
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let navigationController = self.navigationController else { return }
let navBarHeight = navigationController.navigationBar.frame.height
let threshold: CGFloat = 20 // distance from bar where fade-in begins
let alpha = (scrollView.contentOffset.y + navBarHeight + threshold) / threshold
navigationController.navigationBar.subviews.first?.alpha = alpha
}
The magic threshold value is a little hard to explain, but it's basically the distance from the bar at which the fade in will start. A value of 20 means the bar starts to fade in when the scrollView content is 20 points away. A value of 0 would mean the bar snaps straight from fully transparent to fully opaque the moment the scrollView content touches it.
I have an activity indicator that gets presented on an iPhone and iPad. In the iPad in split screen mode it gets presented to whichever side of the view that called it. I would instead like it to get presented in the middle/center the window's screen. If I do it this way wether on the iPhone in portrait or iPad in split screen mode it will always be in the center of the screen.
How do I do this?
MyView: UIViewController{
let actInd = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
#IBAction fileprivate func buttonPressed(_ sender: UIButton) {
guard let window = UIApplication.shared.keyWindow else { return }
//how to add actInd as subview to the window' screen?
actInd.startAnimating()
}
}
It's pretty simple. Turn off the auto-resizing mask. Add the add actInd to window, then set the center anchors.
actInd.translatesAutoresizingMaskIntoConstraints = false
window.addSubview(actInd)
actInd.centerXAnchor.constraint(equalTo: window.centerXAnchor).isActive = true
actInd.centerYAnchor.constraint(equalTo: window.centerYAnchor).isActive = true
Window is subclass of UIView. Just add it as it's subview like you're adding a view to another view. But remember that window is shared throughout your app, so adding it every-time will consume memory, remove it after your job is done.
If you want to center it in the window, you can use autoResizingMask or add constraints to it.
I have a UITabBar and I want to make it blurred. I wrote the following code:
import UIKit
class TabBarController:UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let blur = UIBlurEffect(style: UIBlurEffectStyle.Light)
let blurView = UIVisualEffectView(effect: blur)
blurView.frame = self.view.bounds
blurView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.view.layer.insertSublayer(blurView, atIndex: 0)
}
}
but somehow the last line throws error:
Cannot convert value of type 'UIVisualEffectView' to expected argument
type 'CALayer'
how can I fix that?
I changed the last line to:
self.tabBar.addSubview(blurView)
but now the whole tabbar is blurred (even with icons and they are not visible). When I changed this line to:
self.tabBar.sendSubviewToBack(blurView)
then the tabbar is visible, but not blurred. I want to achieve effect from accepted answer from here Black background on transparent UITabBar but here it is uitabbar and I'm using uitabbarcontroller... Can you help me with applying blur in my case?
You just add the blur view as a subview:
self.view.addSubview(blurView)
Since you just want to blue the tab bar and this class is a tab bar controller, you can do:
self.tabBar.addSubview(blueView)
You also need to change the frame:
blurView.frame = self.tabBar.bounds
why don't you just use the barTintColor property on your TabBarController?
self.tabBar.translucent = true
self.tabBar.barTintColor = UIColor.blackColor()
You don't even need to subclass UITabBarController. You can call this on any UIViewController.
self.tabBarController?.tabBar.translucent = true
self.tabBarController?.tabBar.barTintColor = UIColor.blackColor()
If I understood correctly from the following comment that you posted, you want to change the UITabBar to be black in colour but still blurred.
And yes, I noticed that the UITabBarController is blurred by default, but I would like to make it blurred with specific style (.Dark).
Doing this since iOS 7 has actually become quite easy. Simply change the barStyle of your UITabBar to .black. Put the following code in your UIViewController's viewDidLoad method (note that UITabBar is translucent by default, so you don't need to specify that again).
tabBarController?.tabBar.barStyle = .black
If you want to set it back to the regular, white barStyle, change it back to .default.
tabBarController?.tabBar.barStyle = .default
You may even do this from within Interface Builder by selecting the Tab Bar in your UITabBarController's hierarchy and changing its Style to Black.
I have a solution, all you need is configure your UITabBar as following:
// next code will make tabBar fully transparent
tabBar.isTranslucent = true
tabBar.backgroundImage = UIImage()
tabBar.shadowImage = UIImage() // add this if you want remove tabBar separator
tabBar.barTintColor = .clear
tabBar.backgroundColor = .black // here is your tabBar color
tabBar.layer.backgroundColor = UIColor.clear.cgColor
If you want to add blur, do this:
let blurEffect = UIBlurEffect(style: .dark) // here you can change blur style
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.frame = tabBar.bounds
blurView.autoresizingMask = .flexibleWidth
tabBar.insertSubview(blurView, at: 0)
As a result:
Attach bottom constraint to the bottom of the view instead of Safe Area
It just might not be a problem with your TabBar but with tableView constraints.
Tab bar is blurred by default.
I'm making a photo browsing app, which the user can view the details and comments from the web of the photo being viewed. To do this I have a UIViewController (parentVC) with a segmented control that act as the switch between the details and comments, and the two UITableViewControllers that serve the details and comments. Storyboard of the view controllers in question.
To switch between views, the tableVCs are instantiated from the storyboard and added to the parentVCs as childViewController, and their views are added as subviews.
Since the segmentedControl are placed in the navigationController's navBar, I'd want the tableViews to play nice with the parentVC's navigationbar as well. Which when the first tableview (detailVC) is instantiated, its contentInset is automatically adjusted.
However, when the second tableview (commentsVC) is instantiated following a switch from the segmented control, it's contentInset is not set (in fact it's 0 for all edges), and the tableview is hidden behind the navBar. But when the device is rotated, the tableview adjust its insets and it works fine again.
Right now I suspect the problem has something to do with how automaticallyadjustsscrollviewinsets is done by iOS, since switching which childVC is present first would always set the first VC being presented's contentInset. I don't know if there are any methods I could trigger to set the second VC's contentInset when it's added.
So the question is, how do I have both the childVCs' contentInset set to respect the navigationBar in parentVC? Creating a UIEdgeInset is a workaround but it won't update itself when device orientation is changed. A more minor question would be how should I work with the layouts and insets of navigation controllers when the childVCs are instantiated programmatically so that they work as if they are linked in the storyboard itself.
The following is my code when instantiating the childVCs from the parentVC.
if index == 0 { //details
if detailsVC == nil {
detailsVC = storyboard?.instantiateViewControllerWithIdentifier("InfoPaneDetailsVC") as! InfoPaneDetailsVC
}
if commentsVC != nil {
commentsVC.view.removeFromSuperview()
commentsVC.removeFromParentViewController()
}
addChildViewController(detailsVC)
detailsVC.didMoveToParentViewController(self)
view.addSubview(detailsVC.view)
view.layoutSubviews()
} else if index == 1 { //comments
if commentsVC == nil {
commentsVC = storyboard?.instantiateViewControllerWithIdentifier("InfoPaneCommentsVC") as! InfoPaneCommentsVC
}
if detailsVC != nil {
detailsVC.view.removeFromSuperview()
detailsVC.removeFromParentViewController()
}
addChildViewController(commentsVC)
commentsVC.didMoveToParentViewController(self)
view.addSubview(commentsVC.view)
view.layoutSubviews()
}
the tableview is hidden behind the navBar. But when the device is rotated, the tableview adjust its insets and it
works fine again.
I can duplicate what you are seeing. I don't know why rotating the device gets rid of the underlap*. I can even start the app in landscape orientation, and the TableView will underlap the NavigationBar, then when I rotate to portrait the underlap disappears.
A couple of solutions work for me:
edgesForExtendedLayout = .None
navigationController?.navigationBar.translucent = false
If a ViewController's parent is a NavigationController (or a TabBarController), then two properties determine how the underlap works:
The ViewController's edgesForExtendedLayout property.
The NavigationBar's translucent property.
The edgesForExtendedLayout property has a default value of .All, which means that the ViewController's view will underlap all translucent bars.
You can check whether your NavigationBar is translucent like this:
import UIKit
class InfoViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if navigationController?.navigationBar.translucent == true {
print("NavigationBar is translucent.")
}
if edgesForExtendedLayout == .All {
print("This ViewController's view will underlap a translucent NavigationBar.")
}
I get:
NavigationBar is translucent.
This ViewController's view will underlap a translucent NavigationBar.
The UINavigationBar docs say**,
Additionally, navigation bars are translucent by default. Turning the
translucency off or on does not affect buttons, since they do not have
backgrounds.
You can prevent the underlap like this:
import UIKit
class InfoViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
edgesForExtendedLayout = .None
}
.None tells the ViewController's view not to underlap any translucent bars. Or, you could use .Bottom, which means the view will only underlap a translucent bottom bar, i.e. not a translucent top bar. (To see the various values--or combinations of values--that you can assign to edgesForExtendedLayout check out the docs).
Or, you can prevent the underlap like this:
import UIKit
class InfoViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.translucent = false
}
The value of edgesForExtendedLayout only applies to translucent bars--a view will not underlap an opaque bar. If you don't want to be bothered with having to set translucent to false in your code, you can select the Navigation Bar in the storyboard, and in the Attributes Inspector uncheck Translucent.
* After rotating the device, I printed out edgesForExtendedLayout and the NavBar's translucent property and they didn't change:
class InfoViewController: UIViewController {
override func viewWillTransitionToSize(size: CGSize,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
//executes before transition
coordinator.animateAlongsideTransition(//unlabeled first argument:
{ (UIViewControllerTransitionCoordinatorContext) -> Void in
//executes during transition
},
completion: { (UIViewControllerTransitionCoordinatorContext) -> Void in
//executes after transition
print("After rotating:")
if self.navigationController?.navigationBar.translucent == true {
print("NavigationBar is translucent.")
}
if self.edgesForExtendedLayout == .All {
print("This ViewController's view will underlap a translucent NavigationBar.")
}
}
)
}
--output:--
NavigationBar is translucent.
This ViewController's view will underlap a translucent NavigationBar.
After rotating:
NavigationBar is translucent.
This ViewController's view will underlap a translucent NavigationBar.
** If a NavigationBar has a background image, the value of the NavigationBar's translucent property will be set to true or false depending on the alpha of the image. See the Translucency section in the UINavigationBar docs.
I have programatically created a toolbar to which I have added a UIBarButtonItem.
This is what is currently looks like:
I want the the button to have a border and a corner radius(curved corners).
How will the same be implemented?
UIBarButtonItem Implementation code:
let okBarBtn = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Done, target: self, action: "donePressed:")
If you can find the UIView associated with the UIBarButtonItem, you can modify the UIView.layer. But, finding the UIView is not made easy. I used the technique from Figure out UIBarButtonItem frame in window? . Starting with the navigationController.navigationBar, which itself is a UIView, I recursed through the subviews, one of which will be the button I wanted. Which one? Pick your own criteria! Here's my code:
func recurseViews(view:UIView) {
print("recurseViews: \(view)") // helpful for sorting out which view is which
if view.frame.origin.x > 700 { // find _my_ button
view.layer.cornerRadius = 5
view.layer.borderColor = UIColor.redColor().CGColor
view.layer.borderWidth = 2
}
for v in view.subviews { recurseViews(v) }
}
then in viewDidAppear (or similar)
recurseViews((navigationController?.navigationBar)!)
which is a bit of a hack but does the job:
There's no built-in automatic way to do this, unfortunately. You have two choices:
You could make this a bar button item with a custom view, make that view a UIButton, and then do the usual stuff to that UIButton (which you can do because it's a view), by way of its layer (give it a border and corner radius).
You could just draw (in code) a background image with a curved border and use that as the bar button item's image.
I do it with a storyboard. Just add UIButton inside UiBarButtonItem. Like here
And after that added a border to the UIButton