SearchController’s searchBar added as a UIView subview not being correctly (re)sized - uiview

I’m new to swift dev and stack overflow, I’ll really appreciate if someone could shed some light over a search bar sizing issue I’m having. I’m not being able to correctly size a search bar in the screen. I created a simple version of my project to simplify the reproduction of the odd behavior. It’s a single ViewController with a tableView object to display results of a searchBar search. I don’t want the search bar to roll up along with the table cells, so I created a UIView as placeholder for the searchBar just above the tableView in the Main.storyboard and added necessary constraints to them, so they could be resized correctly with different screen sizes and orientations — indeed, auto-layout seems to be working fine.
Please see the image links as I still can't post images here. Main.storyboard image
I also don’t want to use a UISearchBar object in the storyboard because I want to have control over it in my code later. So I added the SearchController searchBar programmatically as a UIview subview with (apparently all) the needed constraints to be resized along with the UIview. Also, as you can see, tableView and the UIView widths are a little smaller than the screen width, it’s one difference to other posts I found, they were all fully stretched to screen edges — I don’t know if this is important or not. I'm using swift 4 on xcode 9.0.1 (9A1004).
The issue:
As you can see the searchBar right edge passes over the UIView frame right edge. When it's tapped the cancel button passes over the screen limit as well.
searchBar image 1
It's even more weird, when orientation is changed to landscape, searchBar width initially doesn’t stretch with the UIView. But when searchBar is tapped its width returns to pass over the screen limit. And when cancel is tapped searchBar width continues greater than the screen width. This behavior occurs no matter screen size and orientation I choose.
What I tried so far:
This project contains all the recommended properties and constraints I could find, with the exception of the property setTranslatesAutoresizingMaskIntoConstraints which I didn't manage to use. On swift 4, I only found a similar one translatesAutoresizingMaskIntoConstraints, but I couldn’t make it work, it returns “Cannot call value of non-function type 'Bool’”.
// searchController.searchBar.setTranslatesAutoresizingMaskIntoConstraints(true)
searchController.searchBar.translatesAutoresizingMaskIntoConstraints(true) (!) Cannot call value of non-function type 'Bool'
The post Display UISearchController's searchbar programmatically is a very good one, it’s the most similar to mine (except that it extends to the left, and my case it does do the right), but it didn’t work as well. I also realized that #kcstricks sets the searchBar width to self.view.frame.size.width instead of the UIview frame width.
// self.searchController.searchBar.frame.size.width = self.view.frame.size.width
self.searchController.searchBar.frame.size.width = searchBarPlaceholderView.frame.size.width
By changing it, solved the initial searchBar behavior. Now it's initially shown correctly:
searchBar image 2
But once it’s tapped the odd behavior returns and never gets back to normal again.
searchBar image 3
searchBar image 4
I also tried moving the main code to viewDidAppear method, but no results.
Where am I failing at?
Other researched stack overflow posts:
Fixed UISearchBar using UISearchController - Not using header view of UITableView
UISearchController's searchBar doesn't fill full width
UISearchBar width doesn't change when its embedded inside a UINavigationBar
Thank you very much in advance.
Complete code:
// ViewController.swift
// SearchBarTests
// Copyright © 2017 equilibrio. All rights reserved.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchResultsUpdating {
#IBOutlet var myTableView: UITableView!
#IBOutlet var searchBarPlaceholderView: UIView!
var searchController: UISearchController!
var selectedBeers = [Beer]()
struct Beer {
var type = String()
var examples = String()
}
var beers = [Beer(type: "American Lager", examples: "Budweiser, Coors, Pabst Blue Ribbon"),
Beer(type: "German Helles", examples: "Victory Helles Lager, Stoudt's Gold Lager"),
Beer(type: "German Pilsner", examples: "Tröegs Sunshine Pils, Bavaria, Sierra Nevada's Nooner Pilsner"),
Beer(type: "Belgian Gueuze", examples: "")]
// Delegates and Datasources
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.selectedBeers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")
cell.textLabel?.text = self.selectedBeers[indexPath.row].type
cell.detailTextLabel?.text = self.selectedBeers[indexPath.row].examples
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Row \(indexPath.row) selected")
}
func updateSearchResults(for searchController: UISearchController) {
if searchController.searchBar.text! == "" {
selectedBeers = beers
} else {
selectedBeers = beers.filter { $0.type.lowercased().contains(searchController.searchBar.text!.lowercased()) }
}
self.myTableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
selectedBeers = beers
self.searchController = UISearchController(searchResultsController: nil)
self.searchController.searchResultsUpdater = self
self.searchController.dimsBackgroundDuringPresentation = false
self.searchController.searchBar.delegate = self
definesPresentationContext = true
self.searchBarPlaceholderView.addSubview(self.searchController.searchBar)
self.searchController.searchBar.searchBarStyle = UISearchBarStyle.minimal
// self.searchController.searchBar.frame.size.width = self.view.frame.size.width
self.searchController.searchBar.placeholder = "Type desired beer style..."
self.searchController.searchBar.frame.size.width = searchBarPlaceholderView.frame.size.width
self.searchController.searchBar.sizeToFit()
// searchController.searchBar.setTranslatesAutoresizingMaskIntoConstraints(false)
// searchController.searchBar.translatesAutoresizingMaskIntoConstraints(true)
self.myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.myTableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

I had the same issue. I added frame adjusting code to all over the place. For example when i choose an item from tableview suggestion list i call a function. Inside that function i wrote code below, so searchbar came back to width i wanted after animating. I hope somebody can give more professional solution. It doesnt look nice in my code but works.
func(youCallbyTapping) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
var searchBarFrame = self.fromSearchController.searchBar.frame
searchBarFrame.size.width = self.myFrom.frame.size.width - 15
self.fromSearchController.searchBar.frame = searchBarFrame
self.toSearchController.searchBar.frame = searchBarFrame
}
}

Related

Scrolling edge appearance for regular height navigation bar

I was wondering if anyone knows how to get scrolling edge appearance to work on normal height navigation bars (not large title) like in some of Apple's own apps.
In the reminders app, you can see that the navigation bar is clear but as the table/collection view scrolls up, it animates changing the navigation bar to have a translucent background. I know I can get this behaviour using large titles but in my app, but I wanted to mimic the large title with 2 Labels stacked vertically like in the Stocks App.
I've tried the following code:
// default appearance with clear nav bar and no shadow
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor = UIColor.clear
appearance.titleTextAttributes = [.foregroundColor: UIColor.lightText]
// scrolled appearance with the default background
// used when there's content behind the navigation view
let appearance2 = UINavigationBarAppearance()
appearance2.configureWithDefaultBackground()
// appearance2.backgroundColor = UIColor.systemBlue
appearance2.titleTextAttributes = [.foregroundColor: UIColor.lightText]
//use clear style when there's no content behind it
navigationItem.standardAppearance = appearance
//use default style when there's content behind it
navigationItem.scrollEdgeAppearance = appearance2
You can see what I mean in the reminders app when I scroll
Video of Reminders app scrolling navigation bar appearance behaviour
Here's what I have in my app, Ignore the label that appears in the navigation bar as that's changed on scrolling
Video of Simulator scrolling
If you guys can help me, it would be very appreciated
The approach is fairly straight forward according to my test. Refer my code below with a gif of the result shown below
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tv: UITableView!
//Custom TitleView for your navigation bar
var titleLabel: UILabel! // 1
override func viewDidLoad() {
super.viewDidLoad()
tv.delegate = self
tv.dataSource = self
title = nil // 2
navigationController?.navigationBar.prefersLargeTitles = true // 2
// Creating and setting a UILabel as the new titleView
titleLabel = UILabel()
titleLabel.text = "Listen Now"
navigationItem.titleView = titleLabel // 3
// MOST CRUCIAL
tv.contentInset.top = -20 // 4
}
// MARK: UITableView delegates & datasource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.largeContentTitle = "Hello"
return cell
}
}
You will need a UILabel to later, set this as you navigationItem.titleView
Set your title = nil and make sure the title is a Large title
Set your custom title (i.e. - titleLabel in my code) as the titleView
Which is the most crucial part is to set the contentInset.top of your tableView to a value you find fit. The sweet spot was around -20 for me (Reduce this value to bring the cells closer to the navbar), but you can set different values for it. contentInset.top is equivalent to adding top padding in CSS, it adds spacing from the top of the tableview to where the First cell is shown

iOS 11 scroll to top when using large titles doesn't work properly

When using large titles and tapping the status bar to scroll to the top of a UIScrollView or UITableView (probably also UICollectionView, haven't tested this) it always goes a little too far.
I have refresh enabled on my TableView and when tapping the status bar it appears like this and stays that way until I tap the screen.
I have a ScrollView in another ViewController and if I tap the status bar there it also scrolls a little bit too far, making the navigation bar too tall. This also returns to normal when I tap somewhere or scroll a tiny bit.
Normal:
After I tapped the status bar:
This also only happens when I have large titles activated, using normal titles everything works as it should.
Any ideas how to fix this?
How to recreate:
Create a new project with a navigation controller and a UIViewController with a TableView inside.
Set navigation controller to prefer large titles. Turn translucent off. Set title on UIViewController
Set constraints on TableView to pin to the edges of the ViewController
Create outlet for TableView in the ViewController
Implement delegates and set a number of rows, for example 100
Launch app
Scroll down so the large title becomes a normal title
Tap status bar so the tableView scrolls to the top
Now the title is not at the position it should be, if you now scroll a tiny bit up or down it snaps back to the normal position.
ViewController code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath)
return cell
}
}
Okay so I found why the problem occurs, but not how to fix it in that exact scenario.
If you're using large titles and a UITableViewController with the navigation bar translucency set to off the problem will occur.
When you turn translucent back on the problem goes away.
If you're using a TableView in a normal UIViewController the problem always occurs.
Edit
Turns out setting "extendedLayoutIncludesOpaqueBars = true" fixes the problem if you're using a translucent navigation bar!
Similar question: UIRefreshControl() in iOS 11 Glitchy effect
I faced the same issue. I had a collectionview in UIViewController embedded in UINavigationController. Collectionview had leading, trailing, top, bottom constraints to a safe area.
To fix this you need:
Change the top constraint of collectionview to supervises (NOT safe area)
Set extendedLayoutIncludesOpaqueBars = true in viewDidLoad method
After many hours of tests i found a solution that works with UIViewController.
In UIViewController with UITableView, you should:
in viewDidLoad set extendedLayoutIncludesOpaqueBars = true
in storyboard pin tableView top constraint to superview top with constant = 0 (tableView will be under navigationbar and statusbar when navigationbar is translucent)
After that if you tap on statusbar, tableview stops in the right place.
In your ViewController declare a var didBeginScrollToTop of Bool type.
Assign the controller as scrollView's delegate. Then
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
didBeginScrollToTop = true
return true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard didBeginScrollToTop, scrollToTopGestureTargetView?.contentOffset.y ?? 0 < -25 else { return }
scrollToTopGestureTargetView?.setContentOffset(.zero, animated: true)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
didBeginScrollToTop = false
}
What this does is adjusting your content offset when bouncing. The flag is there to catch the scrollToTop gesture and is reset on scroll. You can also set it off after setting the .zero offset. If you have implementation with child VCs, don't forget to call super when overriding those 3 delegate methods.
Now my first approach was to track when the content offSet is < 0, but on some devices it did not expand the navigation item and that magic -25 seemed to work on all simulators. You can combine this approach with an implementation that returns the size of the nav bar and replace it, but as far as I am aware there wasn't an easy way to get the nav bar frame/bounds so easily.
Note you may have to handle also this: Strange velocity prefers large title
I've fix it with maually setting the contentOffset to 0 after scroll to top.
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
return true
}
func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
scrollView.contentOffset.y = 0.0
}

Swift - How creating custom viewForHeaderInSection, Using a XIB file?

I can create simple custom viewForHeaderInSection in programmatically like below. But I want to do much more complex things maybe connection with a different class and reach their properties like a tableView cell. Simply, I want to see what I do.
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if(section == 0) {
let view = UIView() // The width will be the same as the cell, and the height should be set in tableView:heightForRowAtIndexPath:
let label = UILabel()
let button = UIButton(type: UIButtonType.System)
label.text="My Details"
button.setTitle("Test Title", forState: .Normal)
// button.addTarget(self, action: Selector("visibleRow:"), forControlEvents:.TouchUpInside)
view.addSubview(label)
view.addSubview(button)
label.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
let views = ["label": label, "button": button, "view": view]
let horizontallayoutContraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-10-[label]-60-[button]-10-|", options: .AlignAllCenterY, metrics: nil, views: views)
view.addConstraints(horizontallayoutContraints)
let verticalLayoutContraint = NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: 0)
view.addConstraint(verticalLayoutContraint)
return view
}
return nil
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
Is there anyone to explain how can I create a custom tableView header view using xib? I have encountered with old Obj-C topics but I'm new with Swift language. If someone explain as detailed, It would be great.
1.issue: Button #IBAction doesn't connect with my ViewController. (Fixed)
Solved with File's Owner, ViewController base class (clicked left outline menu.)
2.issue: Header height problem (Fixed)
Solved adding headerView.clipsToBounds = true in viewForHeaderInSection: method.
For constraint warnings this answer solved my problems:
When I added ImageView even same height constraint with this method in viewController, it flow over tableView rows look like picture.
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 120
}
If I use, automaticallyAdjustsScrollViewInsets in viewDidLoad, In this case image flows under navigationBar. -fixed-
self.automaticallyAdjustsScrollViewInsets = false
3.issue: If button under View (Fixed)
#IBAction func didTapButton(sender: AnyObject) {
print("tapped")
if let upView = sender.superview {
if let headerView = upView?.superview as? CustomHeader {
print("in section \(headerView.sectionNumber)")
}
}
}
The typical process for NIB based headers would be:
Create UITableViewHeaderFooterView subclass with, at the least, an outlet for your label. You might want to also give it some identifier by which you can reverse engineer to which section this header corresponds. Likewise, you may want to specify a protocol by which the header can inform the view controller of events (like the tapping of the button). Thus, in Swift 3 and later:
// if you want your header to be able to inform view controller of key events, create protocol
protocol CustomHeaderDelegate: class {
func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int)
}
// define CustomHeader class with necessary `delegate`, `#IBOutlet` and `#IBAction`:
class CustomHeader: UITableViewHeaderFooterView {
static let reuseIdentifier = "CustomHeader"
weak var delegate: CustomHeaderDelegate?
#IBOutlet weak var customLabel: UILabel!
var sectionNumber: Int! // you don't have to do this, but it can be useful to have reference back to the section number so that when you tap on a button, you know which section you came from; obviously this is problematic if you insert/delete sections after the table is loaded; always reload in that case
#IBAction func didTapButton(_ sender: AnyObject) {
delegate?.customHeader(self, didTapButtonInSection: section)
}
}
Create NIB. Personally, I give the NIB the same name as the base class to simplify management of my files in my project and avoid confusion. Anyway, the key steps include:
Create view NIB, or if you started with an empty NIB, add view to the NIB;
Set the base class of the view to be whatever your UITableViewHeaderFooterView subclass was (in my example, CustomHeader);
Add your controls and constraints in IB;
Hook up #IBOutlet references to outlets in your Swift code;
Hook up the button to the #IBAction; and
For the root view in the NIB, make sure to set the background color to "default" or else you'll get annoying warnings about changing background colors.
In the viewDidLoad in the view controller, register the NIB. In Swift 3 and later:
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: CustomHeader.reuseIdentifier)
}
In viewForHeaderInSection, dequeue a reusable view using the same identifier you specified in the prior step. Having done that, you can now use your outlet, you don't have to do anything with programmatically created constraints, etc. The only think you need to do (for the protocol for the button to work) is to specify its delegate. For example, in Swift 3:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "CustomHeader") as! CustomHeader
headerView.customLabel.text = content[section].name // set this however is appropriate for your app's model
headerView.sectionNumber = section
headerView.delegate = self
return headerView
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44 // or whatever
}
Obviously, if you're going to specify the view controller as the delegate for the button in the header view, you have to conform to that protocol:
extension ViewController: CustomHeaderDelegate {
func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int) {
print("did tap button", section)
}
}
This all sounds confusing when I list all the steps involved, but it's really quite simple once you've done it once or twice. I think it's simpler than building the header view programmatically.
In matt's answer, he protests:
The problem, quite simply, is that you cannot magically turn a UIView in a nib into a UITableViewHeaderFooterView merely by declaring it so in the Identity inspector.
This is simply not correct. If you use the above NIB-based approach, the class that is instantiated for the root view of this header view is a UITableViewHeaderFooterView subclass, not a UIView. It instantiates whatever class you specify for the base class for the NIBs root view.
What is correct, though, is that some of the properties for this class (notably the contentView) aren't used in this NIB based approach. It really should be optional property, just like textLabel and detailTextLabel are (or, better, they should add proper support for UITableViewHeaderFooterView in IB). I agree that this is poor design on Apple's part, but it strikes me as a sloppy, idiosyncratic detail, but a minor issue given all the problems in table views. E.g., it is extraordinary that after all these years, that we still can't do prototype header/footer views in storyboards at all and have to rely on these NIB and class registration techniques at all.
But, it is incorrect to conclude that one cannot use register(_:forHeaderFooterViewReuseIdentifier:), an API method that has actively been in use since iOS 6. Let’s not throw the baby out with the bath water.
See previous revision of this answer for Swift 2 renditions.
Rob's answer, though it sounds convincing and has withstood the test of time, is wrong and always was. It's difficult to stand alone against the overwhelming crowd "wisdom" of acceptance and numerous upvotes, but I'll try to summon the courage to tell the truth.
The problem, quite simply, is that you cannot magically turn a UIView in a nib into a UITableViewHeaderFooterView merely by declaring it so in the Identity inspector. A UITableViewHeaderFooterView has important features that are key to its correct operation, and a plain UIView, no matter how you may cast it, lacks them.
A UITableViewHeaderFooterView has a contentView, and all your custom subviews must be added to this, not to the UITableViewHeaderFooterView.
But a UIView mysteriously cast as a UITableViewHeaderFooterView lacks this contentView in the nib. Thus, when Rob says "Add your controls and constraints in IB", he is having you add subviews directly to the UITableViewHeaderFooterView, and not to its contentView. The header thus ends up incorrectly configured.
Another sign of the issue is that you are not permitted to give a UITableViewHeaderFooterView a background color. If you do, you'll get this 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.
But in the nib, you cannot help setting a background color on your UITableViewHeaderFooterView, and you do get that message in the console.
So what's the right answer to the question? There's no possible answer. Apple has made a huge goof here. They have provided a method that allows you to register a nib as the source of your UITableViewHeaderFooterView, but there is no UITableViewHeaderFooterView in the Object Library. Therefore this method is useless. It is impossible to design a UITableViewHeaderFooterView correctly in a nib.
This is a huge bug in Xcode. I filed a bug report on this matter in 2013 and it is still sitting there, open. I refile the bug year after year, and Apple keeps pushing back, saying "It has not been determined how or when the issue will be resolved." So they acknowledge the bug, but they do nothing about it.
What you can do, however, is design a normal UIView in the nib, and then, in code (in your implementation of viewForHeaderInSection), load the view manually from the nib and stuff it into the contentView of your header view.
For example, let's say we want to design our header in the nib, and we have a label in the header to which we want to connect an outlet lab. Then we need both a custom header class and a custom view class:
class MyHeaderView : UITableViewHeaderFooterView {
weak var content : MyHeaderViewContent!
}
class MyHeaderViewContent : UIView {
#IBOutlet weak var lab : UILabel!
}
We register our header view's class, not the nib:
self.tableView.register(MyHeaderView.self,
forHeaderFooterViewReuseIdentifier: self.headerID)
In the view xib file, we declare our view to be a MyHeaderViewContent — not a MyHeaderView.
In viewForHeaderInSection, we pluck the view out of the nib, stuff it into the contentView of the header, and configure the reference to it:
override func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
let h = tableView.dequeueReusableHeaderFooterView(
withIdentifier: self.headerID) as! MyHeaderView
if h.content == nil {
let v = UINib(nibName: "MyHeaderView", bundle: nil).instantiate
(withOwner: nil, options: nil)[0] as! MyHeaderViewContent
h.contentView.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
v.topAnchor.constraint(equalTo: h.contentView.topAnchor).isActive = true
v.bottomAnchor.constraint(equalTo: h.contentView.bottomAnchor).isActive = true
v.leadingAnchor.constraint(equalTo: h.contentView.leadingAnchor).isActive = true
v.trailingAnchor.constraint(equalTo: h.contentView.trailingAnchor).isActive = true
h.content = v
// other initializations for all headers go here
}
h.content.lab.text = // whatever
// other initializations for this header go here
return h
}
It's dreadful and annoying, but it is the best you can do.
Create a UITableViewHeaderFooterView and its corresponding xib file.
class BeerListSectionHeader: UITableViewHeaderFooterView {
#IBOutlet weak var sectionLabel: UILabel!
#IBOutlet weak var abvLabel: UILabel!
}
Register the nib similarly to how you register a table view cell. The nib name and reuse identifier should match your file names. (The xib doesn't have a reuse id.)
func registerHeader {
let nib = UINib(nibName: "BeerListSectionHeader", bundle: nil)
tableView.register(nib, forHeaderFooterViewReuseIdentifier: "BeerListSectionHeader")
}
Dequeue and use similarly to a cell. The identifier is the file name.
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "BeerListSectionHeader") as! BeerListSectionHeader
let sectionTitle = allStyles[section].name
header.sectionLabel.text = sectionTitle
header.dismissButton?.addTarget(self, action: #selector(dismissView), for: .touchUpInside)
return header
}
Don't forget the header height.
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return BeerListSectionHeader.height
}
I don't have enough reputation to add comment to Matt answer.
Anyway, the only thing missing here is to remove all subviews from UITableViewHeaderFooterView.contentView before adding new views. This will reset reused cell to initial state and avoid memory leak.

Having multiple tableViews in a scrollview with paging makes tableHeaderView's searchBar non touchable

I have 5 tableViewControllers using UISearchResultsUpdating protocol with the new UISearchController
private var resultSearchController:UISearchController!
resultSearchController = UISearchController(searchResultsController: nil)
resultSearchController.searchResultsUpdater = self
resultSearchController.dimsBackgroundDuringPresentation = false
resultSearchController.searchBar.sizeToFit()
tableView.tableHeaderView = resultSearchController.searchBar
tableView.tableHeaderView?.backgroundColor = UIColor.redColor()
tableView.tableHeaderView?.userInteractionEnabled = true
When I load this viewController by pushing it on top of other viewControllers, everything works fine. I can touch the cells, I can touch the searchBar and operate a search.
Now, when I put 5 (or less, it doesn't matter) of those tableViewControllers inside a controller containing a scrollView
let rect: CGRect = self.view.frame
scrollView = UIScrollView(frame: rect)
scrollView.pagingEnabled = true
scrollView.addSubview(oneTableViewController)
scrollView.addSubview(twoTableViewController)...
I can navigate through the 5 tableViewController by swiping left and right.
I can select a row in any tableview.
but I can't touch the searchBar anymore...
I tried to set:
scrollView.exclusiveTouch = false
ensure that tableView.tableHeaderView?.userInteractionEnabled = true
Any ideas?
By looking at your problem I think there can be a better way to achieve what you are doing.
Instead of using 5 tableview in a scrollview, I will say use one tableview and you can set its cell to show data.. Now you can add scrollview in different cell and add your data on scrollview that will allow you to scroll left and right.
Otherwise if you wanna continue the same approach you are doing then,
you can rotate tableview to 90 degree so that it gives you a feel of left and right scrolling.
You can use your tableview'd delegate:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//do the check
if tableView == YourTableView {
//do your stuff. As you have indexPath you can get row at that indexPath
}
}
If you try to do such thing, don't forget to add the tableViewControllers as childViewControllers of your main ViewController.
self.addChildViewController(tableVC1)
tableVC1.didMoveToParentViewController(self)
self.addChildViewController(tableVC2)
tableVC2.didMoveToParentViewController(self)
now everything works fine!

Swift: Setting height of UIView container to the height of embedded UITableView using prepareForSegue

I have a UITableViewController embedded in a UIView container, which is at the bottom of a UIScrollView in a UIViewController. Here is a screenshot of the current configuration in the storyboard.
The UITableView will contain responses to the selected post. Because the height and number of responses in the UITableView will be variable, I want the UIView container's height to change based on the height of the table. The current (undesirable) behavior is the UIView container stays a constant height, and scrolls when the UITableView height is greater than the UIView containter's height.
The most viable method seems to be having the UITableViewController pass the height of the table to the UIViewController using prepareForSegue, and then set the UIView's height to that passed value. However, I can't seem to set a value with the height of the table until after prepareForSegue is run.
What Isn't Working
Set the identifier of the embed segue between the UIView and the UITableViewController to "embedSegue"
At the top of the the embedded UITableViewController class, create a variable to hold the height of the table:
var tableHeight: CGFloat!
Still in the embedded UITableViewController, override viewDidLoad like so, printing passedHeight to the console for checking:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 68.0
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableHeight = self.tableView.contentSize.height
print(tableHeight)
}
In the UIViewController class that contains the UIView container, create a variable to hold the value passed from the UITableViewController:
var viewHeight: CGFloat!
Still in the UIViewController, override prepareForSegue like so, setting viewHeight to the passed tableHeight value, and printing viewHeight to the console for checking:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "embedSegue" {
let responseTableView = segue.destinationViewController as! ResponseViewController
self.viewHeight = responseTableView.tableHeight
if viewHeight == nil {
print("Nil")
} else {
print(viewHeight)
}
}
}
When I run the app, viewHeight prints as Nil first, then tableHeight prints, which means prepareForSegue has run prior to tableHeight being set.
I have also tried other methods besides prepareForSegue, including calling various functions on the UIView container in viewDidLayoutSubviews() like:
.sizeThatFits()
.needsUpdateConstraints()
.layoutIfNeeded()
However, the UIView container remains the same height as set in the storyboard.
As an epilogue, this is my first StackOverflow post, and I'm excited to be joining the community. Please do let me know if I can clarify my question. Thank you for taking the time.
Instead of using a UIViewController with a UIScrollView containing three UIViews, I created one UITableView with three custom cells. Each cell has their own custom UITableViewCell class for flexibility.
This meant I lost the ability to use constraints to dynamically change the height of each section. I wanted the Post Title and Post Text to be centered on a fullscreen image. To achieve this, I added the logic below:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == 0 {
//Fullscreen image cell - the "20" is for the status bar
return tableView.frame.size.height - self.navigationController!.navigationBar.frame.size.height - 20
} else {
//Just use the current rowHeight
return self.tableView.rowHeight
}
}
References
How to embed a UITableView in a UIScrollview
A short answer to a similar question which sparked the idea
UITableview with more than One Custom Cells with Swift
Detailed explanation of how to create a UITableView with 3 custom UITableViewCells

Resources