iOS 13 navigationItem.titleView broken - ios

So, I have this piece of code that until now (< iOS 12) just adds a blue rectangle in the UINavigationBar of a UINavigationController-embedded UIViewController. In iOS 13 it doesn't work. Is there something that I'm missing? Maybe because it has no given size? Or is there a new method for setting the NavigationBar's view?
navigationItem.titleView = {
let view = UIView()
view.backgroundColor = .blue
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
It looks like this (iOS 12 on the left, iOS 13 on the right):
Actually, while the animation is happening, it's visible but after that it disappears.
Edit:
If you set the titleView in viewDidAppear it works. But with a delay, not so elegant.
This is what I would like to do:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = {
let view = UIView()
view.backgroundColor = .blue
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
}
But what works, as mentioned, is:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationItem.titleView = {
let view = UIView()
view.backgroundColor = .blue
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
}

Related

Swift - Have a "Loading" ViewController in between the NavBar and TabBar while TableView loads its contents

I have a TabBarController as my rootViewController. The first tab is a tableView with a NavigationBar. While the tableView is performing its fetch request and loading its contents, I present a loading viewController with a loading indicator. The loadingVC doesn't cover the tabBar, which is great, but it does cover the NavBar, which I'd like to avoid. I'd essentially like the loadingVC to be placed in between the NavBar and the TabBar, so having its views frame be bound - its top to the bottom of the NavBar and its bottom to the top of the TabBar. I can't get this functionality to work and I thought I'd be able to find a solution in .modalPresentationStyle but the options there don't cover what I'm describing.
Scene Delegate Code:
window = UIWindow(frame: UIScreen.main.bounds)
window?.windowScene = windowScene
let rootVC = TBController()
window?.rootViewController = rootVC
window?.makeKeyAndVisible()
TabBarController:
class TBController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
tabBar.isTranslucent = false
// Article TVC
let articleVC = UINavigationController(rootViewController: ArticlesTVC())
let articleIcon = UITabBarItem(title: "News", image: UIImage(systemName: "newspaper"), tag: 0)
articleVC.tabBarItem = articleIcon
articleVC.navigationBar.prefersLargeTitles = false
articleVC.navigationBar.isTranslucent = false
articleVC.navigationBar.barTintColor = .systemBackground
articleVC.definesPresentationContext = true
articleVC.view.clipsToBounds = true
}
Loading VC:
class LoadingVC: UIViewController {
var loadingLabel: UILabel = {
let loadingLabel = UILabel()
loadingLabel.text = "Loading articles..."
loadingLabel.textAlignment = .center
loadingLabel.translatesAutoresizingMaskIntoConstraints = false
loadingLabel.font = .systemFont(ofSize: 18, weight: .heavy)
loadingLabel.backgroundColor = .systemBackground
return loadingLabel
}()
var loadingActivityIndicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView()
indicator.style = .large
indicator.color = .white
indicator.startAnimating()
indicator.translatesAutoresizingMaskIntoConstraints = false
indicator.backgroundColor = .systemBackground
return indicator
}()
var blurEffectView: UIVisualEffectView = {
let blurEffect = UIBlurEffect(style: .regular)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.clipsToBounds = true
blurEffectView.alpha = 0.8
blurEffectView.translatesAutoresizingMaskIntoConstraints = false
blurEffectView.backgroundColor = .systemBackground
return blurEffectView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
view.addSubview(loadingLabel)
view.addSubview(blurEffectView)
blurEffectView.frame = view.bounds
view.addSubview(loadingActivityIndicator)
loadingActivityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loadingActivityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
view.addSubview(loadingLabel)
loadingLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loadingLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 50).isActive = true
}
}
TableView:
class ArticlesTVC: UITableViewController {
let loadingVC = LoadingVC()
override func viewDidLoad() {
super.viewDidLoad()
loadingVC.modalPresentationStyle = .currentContext
loadingVC.modalTransitionStyle = .crossDissolve
DispatchQueue.main.async {
self.present(self.loadingVC, animated: true)
}
tableView.separatorStyle = .none
tableView.backgroundColor = .systemBackground
}
}
You might be better having it as a view above your tableView and assign true to its isHidden property when your content has loaded. It’s a little messy in the storyboard, although there are ways around that, but in your case you are adding to your view controller’s view programmatically, anyway, so adding your code to a custom view instead and then adding that view over your table view should be no problem. You can then call the custom view’s methods as you please.

UISegmentedControl Corner Radius Not Changing

UISegmentedControl corner radius is not changing. I also followed some answers in this question, my UISegmentedControl's corner radius still is not changing. I followed This tutorial to create UISegmentedControl.
Code:
import UIKit
class SegmentViewController: UIViewController {
private let items = ["Black", "Red", "Green"]
lazy var segmentedConrol: UISegmentedControl = {
let control = UISegmentedControl(items: items)
return control
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupViews()
}
fileprivate func setupViews(){
view.addSubview(segmentedConrol)
segmentedConrol.translatesAutoresizingMaskIntoConstraints = false //set this for Auto Layout to work!
segmentedConrol.heightAnchor.constraint(equalToConstant: 40).isActive = true
segmentedConrol.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 40).isActive = true
segmentedConrol.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -40).isActive = true
segmentedConrol.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
segmentedConrol.selectedSegmentIndex = 1
//style
segmentedConrol.layer.cornerRadius = 20
segmentedConrol.layer.borderWidth = 2
segmentedConrol.layer.borderColor = UIColor.black.cgColor
segmentedConrol.backgroundColor = .red
segmentedConrol.selectedSegmentTintColor = .darkGray
// segmentedConrol.clipsToBounds = true
segmentedConrol.layer.masksToBounds = true
}
}
(PS. Probably the answer is so simple for most people, please do not mind me, I am new in this field.)
Subclass UISegmentedControl and override layoutSubviews. Inside the method set the corner radius to what you want it to be, and you can remove the portion where you set the corner radius in setupViews():
class YourSegmentedControl: UISegmentedControl {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = 20
}
}
In your view controller where you create segmentedControl create an instance of YourSegmentedControl like below.
lazy var segmentedConrol: YourSegmentedControl = {
let control = YourSegmentedControl(items: items)
return control
}()
The result is:

How to add a view as subview for certain controllers

I have multiple storyboards in my app. I want to add a view on always on the top just below the navigation bar for some of the controllers. How Can I achieve this?
I already used navigation delegate and add a view in the window but no luck. Steps to show the gray view in the attached image is.
1. On click of a button on that view controller; a gray view should show and remain on the top of the controllers until all the scanning of the device is not done whether the user should go any of the viewControllers.
You can create a UINavigationController subclass and add the view in it.
class NavigationController: UINavigationController {
let customView = UIView()
let iconImgView = UIImageView()
let msgLbl = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
customView.isHidden = true
customView.translatesAutoresizingMaskIntoConstraints = false
customView.backgroundColor = .gray
view.addSubview(customView)
iconImgView.contentMode = .scaleAspectFit
iconImgView.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(iconImgView)
msgLbl.numberOfLines = 0
msgLbl.lineBreakMode = .byWordWrapping
msgLbl.textColor = .white
msgLbl.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(msgLbl)
customView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
iconImgView.widthAnchor.constraint(equalToConstant: 40).isActive = true
iconImgView.heightAnchor.constraint(equalToConstant: 40).isActive = true
iconImgView.centerYAnchor.constraint(equalTo: customView.centerYAnchor).isActive = true
iconImgView.leadingAnchor.constraint(equalTo: customView.leadingAnchor, constant: 15).isActive = true
iconImgView.trailingAnchor.constraint(equalTo: msgLbl.leadingAnchor, constant: 15).isActive = true
msgLbl.topAnchor.constraint(equalTo: customView.topAnchor, constant: 10).isActive = true
msgLbl.bottomAnchor.constraint(equalTo: customView.bottomAnchor, constant: 10).isActive = true
msgLbl.trailingAnchor.constraint(equalTo: customView.trailingAnchor, constant: -15).isActive = true
msgLbl.heightAnchor.constraint(greaterThanOrEqualToConstant: 30).isActive = true
}
func showCustomView(message: String, icon: UIImage) {
msgLbl.text = message
iconImgView.image = icon
customView.isHidden = false
}
func hideCustomView() {
customView.isHidden = true
}
}
Embed all your view controllers in this navigation controller. When you want to show/hide the gray view in a view controller use
Show
(self.navigationController as? NavigationController)?.showCustomView(message: "Any Message", icon: UIImage(named: "anyImage")!)
Hide
(self.navigationController as? NavigationController)?.hideCustomView()
When you push another view controller from the same navigation controller the view won't be hidden until you call the hide method
You can simply create a custom UIView with the relevant frame and call addSubview() on the view you want to add it to.
lazy var customView: UIView = {
let customView = UIView(frame: CGRect.init(x: 0, y: self.view.safeAreaInsets.top, width: UIScreen.main.bounds.width, height: 100))
customView.backgroundColor = .gray
return customView
}()
#IBAction func onTapButton(_ sender: UIButton) {
self.view.addSubview(customView)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.customView.removeFromSuperview()
}
To add it below the navigationBar, use y position of frame as self.view.safeAreaInsets.top. With this your customView will always be aligned below the navigationBar.
You can create the view with the height as per your requirement. I've used height = 100.
Give the correct frame and you can add any view as a subView to another view.

Custom navigation bar title gets clipped after user leaves view controller

I need to extend my navigation bar height but since Apple made it very hard to change the navigation bar height in iOS 11 I decided I needed to use a custom view which extended the navigation bar without the user noticing.
I've created a custom view to add to the bottom of the navigation bar. I made it red just for the sake of making this question more clear. When the user leaves the view controller and then comes back, the title view custom view is "clipped" by the red view. Why?
I've tried to set clipsToBounds false on the custom title view, but that didn't help. How can I make sure the custom title view always stays on top of everything? Why is it being clipped and overlapped by the little red view (whose main purpose is to "extend" the navigation bar)?
Note: "Monthly Spending" label is part of the title view being clipped.
class ViewController: UIViewController {
let customTitleView = CustomTitleView()
let navigationBarExtensionView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
setupAdditionalGradientView()
navigationItem.titleView = customTitleView
}
internal func setupAdditionalGradientView() {
view.addSubview(navigationBarExtensionView)
navigationBarExtensionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
navigationBarExtensionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
navigationBarExtensionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
navigationBarExtensionView.heightAnchor.constraint(equalToConstant: 18).isActive = true
// Hide pixel shadow between nav bar and red bar
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.layer.shadowRadius = 0
navigationController?.navigationBar.layer.shadowOffset = CGSize(width: 0, height: 0)
}
}
Custom title view:
import UIKit
class CustomTitleView: UIView {
let primaryLabel: UILabel = {
let label = UILabel()
label.text = "$10,675.00"
label.font = UIFont.systemFont(ofSize: 27.99, weight: .medium)
label.textColor = .white
label.textAlignment = .center
return label
}()
let secondaryLabel: UILabel = {
let label = UILabel()
label.text = "Monthly Spending"
label.textColor = .white
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 10, weight: .medium)
return label
}()
let stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.alignment = .center
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupStackView()
}
internal func setupStackView() {
addSubview(stackView)
stackView.addArrangedSubview(primaryLabel)
stackView.addArrangedSubview(secondaryLabel)
stackView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 10).isActive = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
In iOS 11, a custom bar button item view such as your titleView is sized from the inside out using constraints. Thus, you need constraints to size the view correctly. You are not providing any constraints, so the runtime doesn't know how to size the title view.
However, I would suggest that you just give up on the dubious idea of extending your UINavigationItem's custom view downward below the outside of the navigation bar, and instead, just show the words Monthly Spending in your view controller's view.

UIStackView doesn't fill if removing and adding the same views

I found an issue in iOS 9 where the layout is not working properly when removing and adding the same views to UIStackView and uses intrinsic content size of the views instead of the specified fillEqually distribution type.
For example, this code:
class ViewController: UIViewController {
let stackView = UIStackView()
let red = UIButton()
let blue = UIButton()
let green = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
stackView.distribution = .fillEqually
red.backgroundColor = .red
blue.backgroundColor = .blue
green.backgroundColor = .green
let views = [red, blue, green]
for view in views {
stackView.addArrangedSubview(view)
}
view.backgroundColor = .lightGray
view.addSubview(stackView)
stackView.frame = view.bounds.insetBy(dx: 0, dy: view.frame.height / 3)
let button = UIButton()
button.frame = view.bounds
view.addSubview(button)
button.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)
}
func buttonClicked() {
for view in stackView.arrangedSubviews {
view.removeFromSuperview()
}
stackView.addArrangedSubview(red)
stackView.addArrangedSubview(blue)
}
}
Shows the following result after tapping the screen:
But on iOS 10 it shows the expected result:
Creating new views instead of reusing the old ones outputs the expected layout on iOS 9:
func buttonClicked() {
for view in stackView.arrangedSubviews {
view.removeFromSuperview()
}
let red = UIButton()
let blue = UIButton()
red.backgroundColor = .red
blue.backgroundColor = .blue
stackView.addArrangedSubview(red)
stackView.addArrangedSubview(blue)
}
But the whole point of this code is reusing views to improve performance.
Is this an issue in my code? A bug on iOS 9? Is there any known workaround?
for view in stackView.arrangedSubviews {
stackView.removeArrangedSubview(view)
view.removeFromSuperview()
}
I think you forget to call removeArrangedSubview.
Solved it by explicitly calling layoutIfNeeded method after removing all the views:
func buttonClicked() {
for view in stackView.arrangedSubviews {
view.removeFromSuperview()
}
stackView.layoutIfNeeded() // Required to avoid layout issues in iOS 9
stackView.addArrangedSubview(red)
stackView.addArrangedSubview(blue)
}

Resources