I am facing an issue where I can see a black portion in my UIViewController where I have a UISearchController, UICollectionView and UISegmentControl because of my poor design.
I have added a UISearchController in navigationbar using following code:
func setupSearchBar(){
navigationItem.searchController = taskSearchController
taskSearchController.searchBar.tintColor = .white
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes = [NSAttributedStringKey.foregroundColor.rawValue: UIColor.lightGray]
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).attributedPlaceholder = NSAttributedString(string: "Search Text", attributes: [NSAttributedStringKey.foregroundColor: UIColor.lightGray])
if let textfield = taskSearchController.searchBar.value(forKey: "searchField") as? UITextField {
if let backgroundview = textfield.subviews.first {
backgroundview.backgroundColor = UIColor.init(white: 1, alpha: 1)
backgroundview.layer.cornerRadius = 10
backgroundview.clipsToBounds = true
}
}
taskSearchController.hidesNavigationBarDuringPresentation = true
//navigationItem.hidesSearchBarWhenScrolling = false
//taskSearchController.searchBar.scopeButtonTitles = ["ASSIGNED TASK","CREATED TASK"]
}
after adding UISearchController I am adding a UISegmentControl and a UICollectionView using following code:
func setupView(){
//self.view.addSubview(coverView)
self.view.addSubview(taskSegment)
taskSegment.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
taskSegment.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
taskSegment.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
taskSegment.heightAnchor.constraint(equalToConstant: 30).isActive = true
self.view.addSubview(collectionView)
collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: taskSegment.bottomAnchor, constant: 10).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 5).isActive = true
}
This my initial view which is fine.
This is how it looks when UISearchController is focused
Here you can see when navigationBar hides and UISearchController placed at top at that time my UISegment and UIcollectionView remains at same position so I want to move it according to change in UISearchController.
Can anyone tell me what should I do?
Thank You in advance.
It hides the navigation bar because hideNavigationBarDuringPresentation is set to true. This is intended behavior.
You either need to disable this so that the view constraints dont change during presentation or you need to amend your constraints so that the view would adjust to the height change of the navigation bar. You should be able to do this quite easily by setting a top constraint to the Safe area of the main view.
Related
I am trying to replicate the visual effect that apple uses with several of its applications where the navigation bar and a toolbar are combined. Here is an example:
The peculiarity is that the two have and share a somewhat transparent background effect, say .configureWithDefaultBackground() (In the picture you can see how the navigation bar and the toolbar share the same background blur effect)
Trying to replicate the design, when the content of the collection has not yet started to scroll, the background color of the toolbar does not behave like the navigation bar and when it does start scrolling, the effect is shared. Here are some images
Trying to replicate the design, when the content of the collection has not yet started to scroll, the background color of the toolbar does not behave like the navigation bar and it has a "grey" background color while in the apple one, before you start to scroll, both are white (or .systemBackground)
When you start scrolling, the two share the same background effect and behave as expected.
Here's some code:
private func configureNavBar() {
let navigationBarAppearanceStandard = UINavigationBarAppearance()
navigationBarAppearanceStandard.configureWithDefaultBackground()
let navigationBarAppearanceScrollEdge = UINavigationBarAppearance()
navigationBarAppearanceScrollEdge.configureWithOpaqueBackground()
navigationController?.navigationBar.standardAppearance = navigationBarAppearanceStandard
navigationController?.navigationBar.scrollEdgeAppearance = navigationBarAppearanceScrollEdge
navigationController?.navigationBar.standardAppearance.shadowColor = .clear
navigationController?.navigationBar.scrollEdgeAppearance?.shadowColor = .clear
}
func configureToolbar() {
let toolbarAppearance = UIToolbarAppearance()
toolbarAppearance.configureWithTransparentBackground() // .default & .opaque behave the same, gray color before scrolling
toolbar.scrollEdgeAppearance = toolbarAppearance
toolbar.compactAppearance = toolbarAppearance
toolbar.delegate = self
toolbar.translatesAutoresizingMaskIntoConstraints = false
}
private func layoutUI() {
view.addSubview(collectionView)
view.addSubview(toolbar)
NSLayoutConstraint.activate([
toolbar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
toolbar.heightAnchor.constraint(equalToConstant: 50),
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
collectionView.contentInset.top = 50
collectionView.verticalScrollIndicatorInsets.top = 50
}
Tried self.toolbar.setBackgroundImage(UIImage(), forToolbarPosition: .top, barMetrics: .default), but makes it transparent and not blurry.
Does anyone know why the toolbar is grayed out and not the same color as the navigation bar before you start scrolling the collectionView?
What is the easiest way to setup a custom NavigationBar in iOS/Swift?
I want to use a label above the navigation buttons (increased height). Is the only way to replace and hide the default NavigationBar and use an UIView?
(There's some missing things in your question, so this may not answer it.)
A UINavigationBar is simply a subclass of UIView, just as a UINavigationController is a subclass of a UIViewController. Where the former is just a "view" with a title and left/right bar buttons, the latter will have that and can also "control" a UIViewController stack.
Here's code for the former.
First, let's declare a label for the user name and a navigation bar with no title and a single left/right bar button. You can have an array of bar buttons on the left/right if you want. Also, I like to have SF Symbols with descriptive text, so I'll make UIButtons first.
let userName = UILabel()
let navBar = UINavigationBar()
let navItems = UINavigationItem(title: "")
var btnLeft:UIButton()
var btnRight:UIButton()
var barBtnLeft:UIBarButtonItem!
var barBtnRight:UIBarButtonItem!
Now let's populate things and set things up for auto layout. This all can be done during viewDidLoad, and change things (like user name) in other places.
// user name label
userName.translatesAutoresizingMaskIntoConstraints = false
userName.text = "User Name"
userName.textAlignment = .center
// create UIButtons with an SF Symbol and description
btnLeft.translatesAutoresizingMaskIntoConstraints = false
btnLeft.setImage(UIImage(systemName: "lessthan"), for: .normal)
btnLeft.setTitle(" left", for: .normal)
btnLeft.setTitleColor(.blue, for: .normal)
btnRight.translatesAutoresizingMaskIntoConstraints = false
btnRight.setImage(UIImage(systemName: "greaterthan"), for: .normal)
btnRight.setTitle(" right", for: .normal)
btnRight.setTitleColor(.blue, for: .normal)
// I like to "frame" these buttons, this is optional
btnLeft.layer.borderWidth = 1
btnLeft.layer.borderColor = UIColor.blue.cgColor
btnLeft.layer.cornerRadius = 5
btnRight.layer.borderWidth = 1
btnRight.layer.borderColor = UIColor.blue.cgColor
btnRight.layer.cornerRadius = 5
// now give the buttons a size
btnLeft.heightAnchor.constraint(equalToConstant: 44).isActive = true
btnLeft.widthAnchor.constraint(equalToConstant: 100).isActive = true
btnRight.heightAnchor.constraint(equalToConstant: 44).isActive = true
btnRight.widthAnchor.constraint(equalToConstant: 100).isActive = true
// don't forget to add targets!
btnLeft.addTarget(self, action: #selector(leftButtonSelector), for: .touchUpInside)
btnRight.addTarget(self, action: #selector(rightButtonSelector), for: .touchUpInside)
// make the result be a bar button
barBtnLeft = UIBarButtonItem(customView: btnLeft)
barBtnRight = UIBarButtonItem(customView: btnRight)
// add them to the navigation bar
navItems.leftBarButtonItems = [barBtnLeft]
navItems.rightBarButtonItems = [barBtnRight]
navBar.setItems([navItems], animated: false)
navBar.translatesAutoresizingMaskIntoConstraints = false
// add the label and navigation bar to your view
view.addSubview(userName)
view.addSubview(navBar)
// finally, lay the user name above the navigation bar
userName.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
userName.heightAnchor.constraint(equalToConstant: 44).isActive = true
userName.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
userName.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
navBar.topAnchor.constraint(equalTo: userName.bottomAnchor).isActive = true
navBar.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
navBar.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
So I have a scrollView, inside that scrollView is a stackView that contains every views in the screen.
The problem is my stackView has many hide-able views so I need to adjust my containsVieww's height base on my stackView's height
my psuedo code should be like this :
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(stackView.bounds.height)
// do logic to change contentView size here
}
func setupView(){
scrollView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
scrollView.backgroundColor = .white
scrollView.contentSize = CGSize(width: view.frame.width, height: view.frame.height)
scrollView.addSubview(stackView)
stackView.setArrangedSubView(views: [label1, label 2, label 3, label 4, ..., label n]
[label1, label 2, label 3].isHidden = true
}
func onHitButton(){
if isHidden {
[label1, label 2, label 3].isHidden = false
isHidden = false
} else {
[label1, label 2, label 3].isHidden = true
isHidden = true
}
print(stackView.bounds.height) // still return the ex height
}
Here is the problem:
On first init, my [label1,2,3].isHidden = true, my stackViewHeight is 500
When my onHitButton is called, my [label1,2,3].isHidden = false, my stackViewHeight is still 500 but the screen displays correctly, those labels are visible now and my stackView is stretched. And of course my scrollView is not displays correctly.
Then I hit my onHitButton again, those labels are hidden, my stackView is shrink on screen but the stackViewHeight returned 850?
It's supposed to be the other way around.
I also tried to print the height on another button call and it return the right height? Seem like viewDidLayoutSubviews called too early.
To sum it up: stackView return the height before it resize it self
Use auto-layout to constrain the stack view to the scroll view's Content Layout Guide:
scrollView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
// reference to scrollView's Content Layout Guide
let cGuide = scrollView.contentLayoutGuide
// reference to scrollView's Frame Layout Guide
let fGuide = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain stackView to scrollView's Content Layout Guide
stackView.topAnchor.constraint(equalTo: cGuide.topAnchor),
stackView.leadingAnchor.constraint(equalTo: cGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: cGuide.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: cGuide.bottomAnchor),
// constrain stackView's Width to scrollView's Frame Layout Guide
stackView.widthAnchor.constraint(equalTo: fGuide.widthAnchor),
])
This will completely avoid the need to ever set .contentSize -- it will all be handled by auto-layout.
I Have two viewControllers. One is home page, other is detail page. if I make them window's root view controller they look like this:
This is My Home Page
This is my details page
but when I make my home page my root view controller and push my detail view controller to present, it looks like this :
So here is my detailviewController. It has one scrollview and uistackview. no storyboard used.
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let scrollViewContainer: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
and this is the constraints:
view.addSubview(scrollView)
scrollView.addSubview(scrollViewContainer)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// this is important for scrolling
scrollViewContainer.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
I think it is iOS 13 related problem.
EDIT: If I make animated to false when pushing detailvc, everything is normal.
If the problem is really in constraints, try to set scrollView - widthAnchor equal to view width and centerXAnchor equal to view center X.
It seems to me wrong to set the following at the same time:
scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
and
scrollViewContainer.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
I suggest leaving the last constraint and also align the scrollViewContainer in the centerX relative to the view.
I hope it will help you.
I've searched through S/OF and can't find a fix for the TableView being behind my TabBar.
I set up my TableView like this;
func setUpTableView() {
messagesTableView.frame = view.frame
messagesTableView.backgroundColor = .white
view.addSubview(messagesTableView)
messagesTableView.tableFooterView = UIView()
messagesTableView.delegate = self
messagesTableView.dataSource = self
messagesTableView.register(UITableViewCell.self, forCellReuseIdentifier: messagesCellIdentifier)
edgesForExtendedLayout = []
extendedLayoutIncludesOpaqueBars = false
messagesTableView.contentInsetAdjustmentBehavior = .never
}
Then I setUpTableView() in viewDidLoad().
According to all sources
edgesForExtendedLayout = []
extendedLayoutIncludesOpaqueBars = false
messagesTableView.contentInsetAdjustmentBehavior = .never
Should satisfy the content insets and not allow the TableView to scroll behind the TabBar.
Please note my TabBars' translucency is set to false inside TabBarController.
tabBar.isTranslucent = false
As always any help appreciated.
Adding Illustration
When scrolling to the bottom is not showing the complete TableView content as the TabBar covers the last few indexes.
Did you try this in viewDidAppear ?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
}
Edit
Remove
messagesTableView.frame = view.frame
and add autoLayout to your messagesTableView
messagesTableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
messagesTableView.topAnchor.constraint(equalTo: topAnchor),
messagesTableView.leftAnchor.constraint(equalTo: leftAnchor),
messagesTableView.bottomAnchor.constraint(equalTo: bottomAnchor),
messagesTableView.rightAnchor.constraint(equalTo: rightAnchor)
])