swift tvOS vertical scroll Grid - swift

I am new to tvOS development and I would like to create a Grid with 4 items per row. The collection should vertically scroll. My challenge is that I'm not sure what to do with the information provided by Apple here in this doc as there is no example.
In iOS we would use something like:
class GridFlowLayout: UICollectionViewFlowLayout {
// here you can define the height of each cell
let itemHeight: CGFloat = 160
override init() {
super.init()
setupLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupLayout()
}
/**
Sets up the layout for the collectionView. 1pt distance between each cell and 1pt distance between each row plus use a vertical layout
*/
func setupLayout() {
minimumInteritemSpacing = 1
minimumLineSpacing = 1
scrollDirection = .vertical
}
var itemWidth: CGFloat {
return collectionView!.frame.width / 4 - 1
}
override var itemSize: CGSize {
set {
self.itemSize = CGSize(width: itemWidth, height: itemHeight)
}
get {
return CGSize(width: itemWidth, height: itemHeight)
}
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
return collectionView!.contentOffset
}
}
Then assign it to the collectionView layout
collectionView.collectionViewLayout = GridFlowLayout()
Im not sure what to do for tvOS how should I adapt the above code to work for tvOS using programmatic code.

Related

Is there a way to give outer border in every section of uicollctionview?

Try to add outer border of every section in a collection view.
If i'm using cell.layer.border, it will also create an inner border. Is there a simple way to create outer border only for every section in collection view?
Try to created red border like image below
As Matt pointed out in the comments and the articles pointed out, you would need to make use of a DecorationView.
You can read up on this here
So to do this, you would have to follow these steps:
Create a custom UICollectionReusableView which would serve as your decoration view
Subclass UICollectionViewFlowLayout to create a custom layout
Override layoutAttributesForDecorationView and layoutAttributesForElements to figure out the frame of each section and place the decoration view in the section frame
Use the custom flow layout as the layout of your collection view
Here is that in code
Create the Decoration view, which is just a regular view with a border
class SectionBackgroundView : UICollectionReusableView {
static let DecorationViewKind = "SectionBackgroundIdentifier"
override init(frame: CGRect) {
super.init(frame: frame)
// Customize the settings to what you want
backgroundColor = .clear
layer.borderWidth = 5.0
layer.borderColor = UIColor.blue.cgColor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Create a custom flow layout
class BorderedFlowLayout: UICollectionViewFlowLayout {
override init() {
super.init()
// Register your decoration view for the layout
register(SectionBackgroundView.self,
forDecorationViewOfKind: SectionBackgroundView.DecorationViewKind)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutAttributesForDecorationView(ofKind elementKind: String,
at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if elementKind == SectionBackgroundView.DecorationViewKind {
guard let collectionView = collectionView else { return nil }
// Initialize a UICollectionViewLayoutAttributes for a DecorationView
let decorationAttributes
= UICollectionViewLayoutAttributes(forDecorationViewOfKind: SectionBackgroundView.DecorationViewKind,
with:indexPath)
// Set it behind other views
decorationAttributes.zIndex = 2
let numberOfItemsInSection
= collectionView.numberOfItems(inSection: indexPath.section)
// Get the first and last item in the section
let firstItem = layoutAttributesForItem(at: IndexPath(item: 0, section: indexPath.section))
let lastItem = layoutAttributesForItem(at: IndexPath(item: (numberOfItemsInSection - 1),
section: indexPath.section))
// The difference between the maxY of the last item and
// the the minY of the first item is the height of the section
let height = lastItem!.frame.maxY - firstItem!.frame.minY
// Set the frame of the decoration view for the section
decorationAttributes.frame = CGRect(x: 0,
y: firstItem!.frame.minY,
width: collectionView.bounds.width,
height: height)
return decorationAttributes
}
return nil
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// Get all the UICollectionViewLayoutAttributes for the current view port
var attributes = super.layoutAttributesForElements(in: rect)
// Filter to get all the different sections
let sectionAttributes
= attributes?.filter { $0.indexPath.item == 0 } ?? []
// Loop through the different sections
for sectionAttribute in sectionAttributes {
// Create decoration attributes for the current section
if let decorationAttributes
= self.layoutAttributesForDecorationView(ofKind: SectionBackgroundView.DecorationViewKind,
at: sectionAttribute.indexPath) {
// Add the decoration attributes for a section if it is in the current viewport
if rect.intersects(decorationAttributes.frame) {
attributes?.append(decorationAttributes)
}
}
}
return attributes
}
}
Make use of the custom layout in your view controller
private func configureCollectionView() {
collectionView = UICollectionView(frame: CGRect.zero,
collectionViewLayout: createLayout())
collectionView.backgroundColor = .white
collectionView.register(UICollectionViewCell.self,
forCellWithReuseIdentifier: "cell")
// You can ignore the header and footer views as you probably already did this
collectionView.register(HeaderFooterView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: HeaderFooterView.identifier)
collectionView.register(HeaderFooterView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter,
withReuseIdentifier: HeaderFooterView.identifier)
collectionView.dataSource = self
collectionView.delegate = self
view.addSubview(collectionView)
}
private func createLayout() -> UICollectionViewFlowLayout {
let flowLayout = BorderedFlowLayout()
flowLayout.minimumLineSpacing = 10
flowLayout.minimumInteritemSpacing = 10
flowLayout.scrollDirection = .vertical
flowLayout.sectionInset = UIEdgeInsets(top: 10,
left: horizontalPadding,
bottom: 10,
right: horizontalPadding)
return flowLayout
}
Doing all of this should give you what you want
I have only posted the most important snippets. If for some reason you can't follow along, here is the full code to recreate the example

How to create this view with constraints programtically

I feel this is pretty simple to accomplish but I can't seem to figure it out. I'm fairly new to not using the storyboard and trying to learn how to set my constraints programatically for my views. I created the view that I want easily in storyboard but can't seem to get it programatically.
I have my view controller has my parent view, and then I call a container view. I imagine in the container view is where I setup my constraints but I can't get the height of my view to stay the same every-time I change to a different device
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var clariView = ClariContainerView()
view.addSubview(clariView)
}
}
This my view controller and then my ClariContainerView looks like this:
class ClariContainerView: UIView {
lazy var clariQuestionView: UIView = {
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 0))
containerView.backgroundColor = .blue
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
public func setupView() {
addSubview(clariQuestionView)
setupLayout()
}
public func setupLayout() {
NSLayoutConstraint.activate([
clariQuestionView.heightAnchor.constraint(equalToConstant: 169)
])
}
}
What I'm trying to recreate is this:
I need the height of the blue view to always be 169.
Here is how you would do that:
First, you don't need to define a frame for your containerView since the translatesAutoresizingMaskIntoConstraints = falsestatement is specifying that you'll be using auto-layout and therefore the frame will be ignored:
lazy var clariQuestionView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .blue
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
And here is how you would define your constraints. You need to set height, but also need to pin the view to the bottom, the leading, and the trailing edges of self.view:
public func setupLayout() {
NSLayoutConstraint.activate([
clariQuestionView.heightAnchor.constraint(equalToConstant: 169),
clariQuestionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
clariQuestionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
clariQuestionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
])
}
For such a basic layout you don't really need to add heightAnchor. Here is a simple way to achieve desired behavior + bonus — a code snippet to adjust height according to the device's safeAreaInsets.
class ClariContainerView: UIView {
lazy var clariQuestionView: UIView = {
let desiredContainerHeigh = 169
// If you want, you can use commented code to adjust height according to the device's safe area.
// This might be needed if you want to keep the same height over safe area on all devices.
// let safeAreaAdjustment = UIApplication.shared.keyWindow?.rootViewController?.view.safeAreaInsets.bottom ?? 0
let containerView = UIView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height - 169, width: UIScreen.main.bounds.width, height: 169))
containerView.backgroundColor = .blue
containerView.translatesAutoresizingMaskIntoConstraints = true
return containerView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
public func setupView() {
addSubview(clariQuestionView)
}
}

How to make a Self-sizing UiImageView?

I have a need for a simple QR Code class that I can re-use. I have created the class and it works, however manually setting the size constraints is not desired because it needs to adjust its size based on the DPI of the device. Here in this minimal example, I just use 100 as the sizing calculation code is not relevant (set to 50 in IB). Also I will have multiple QR Codes in different positions, which I will manage their positioning by IB. But at least I hope to be able to set the width and height constraints in code.
The below code shows a QR code, in the right size (set at runtime), but when the constraints are set to horizontally and vertically center it, it does not. Again, I don't want the size constraints in the IB, but I do want the position constraints in the IB
import Foundation
import UIKit
#IBDesignable class QrCodeView: UIImageView {
var content:String = "test" {
didSet {
generateCode(content)
}
}
lazy var filter = CIFilter(name: "CIQRCodeGenerator")
lazy var imageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = CGRect(x:0, y:0, width:100, height:100)
frame = CGRect(x:frame.origin.x, y:frame.origin.y, width:100, height:100)
}
func setup() {
//translatesAutoresizingMaskIntoConstraints = false
generateCode(content)
addSubview(imageView)
layoutIfNeeded()
}
func generateCode(_ string: String) {
guard let filter = filter,
let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
return
}
filter.setValue(data, forKey: "inputMessage")
guard let ciImage = filter.outputImage else {
return
}
let transform = CGAffineTransform(scaleX: 10, y: 10)
let scaled = UIImage(ciImage: ciImage.transformed(by: transform))
imageView.image = scaled
}
}
I believe you're making this more complicated than need be...
Let's start with a simple #IBDesignable UIImageView subclass.
Start with a new project and add this code:
#IBDesignable
class MyImageView: UIImageView {
// we'll use this later
var myIntrinsicSize: CGSize = CGSize(width: 100.0, height: 100.0)
override var intrinsicContentSize: CGSize {
return myIntrinsicSize
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
self.image = UIImage()
}
func setup() {
backgroundColor = .green
contentMode = .scaleToFill
}
}
Now, in Storyboard, add a UIImageView to a view controller. Set its custom class to MyImageView and set Horizontal and Vertical Center constraints.
The image view should automatically size itself to 100 x 100, centered in the view with a green background (we're just setting the background so we can see it):
Run the app, and you should see the same thing.
Now, add it as an #IBOutlet to a view controller:
class ViewController: UIViewController {
#IBOutlet var testImageView: MyImageView!
override func viewDidLoad() {
super.viewDidLoad()
testImageView.myIntrinsicSize = CGSize(width: 300.0, height: 300.0)
}
}
Run the app, and you will see a centered green image view, but now it will be 300 x 300 points instead of 100 x 100.
The rest of your task is pretty much adding code to set this custom class's .image property once you've rendered the QRCode image.
Here's the custom class:
#IBDesignable
class QRCodeView: UIImageView {
// so we can test changing the QRCode content in IB
#IBInspectable
var content:String = "test" {
didSet {
generateCode(content)
}
}
var qrIntrinsicSize: CGSize = CGSize(width: 100.0, height: 100.0)
override var intrinsicContentSize: CGSize {
return qrIntrinsicSize
}
lazy var filter = CIFilter(name: "CIQRCodeGenerator")
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
generateCode(content)
}
func setup() {
contentMode = .scaleToFill
}
override func layoutSubviews() {
super.layoutSubviews()
generateCode(content)
}
func generateCode(_ string: String) {
guard let filter = filter,
let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
return
}
filter.setValue(data, forKey: "inputMessage")
guard let ciImage = filter.outputImage else {
return
}
let scX = bounds.width / ciImage.extent.size.width
let scY = bounds.height / ciImage.extent.size.height
let transform = CGAffineTransform(scaleX: scX, y: scY)
let scaled = UIImage(ciImage: ciImage.transformed(by: transform))
self.image = scaled
}
}
In Storyboard / IB:
And here's an example view controller:
class ViewController: UIViewController {
#IBOutlet var qrCodeView: QRCodeView!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// calculate your needed size
// I'll assume it ended up being 240 x 240
qrCodeView.qrIntrinsicSize = CGSize(width: 240.0, height: 240.0)
}
}
Edit
Here's a modified QRCodeView class that will size itself to a (physical) 15x15 mm image.
I used DeviceKit from https://github.com/devicekit/DeviceKit to get the current device's ppi. See the comment to replace it with your own (assuming you are already using something else).
When this class is instantiated, it will:
get the current device's ppi
convert ppi to pixels-per-millimeter
calculate 15 x pixels-per-millimeter
convert based on screen scale
update its intrinsic size
The QRCodeView (subclass of UIImageView) needs only position constraints... so you can use Top + Leading, Top + Trailing, Center X & Y, Bottom + CenterX, etc, etc.
#IBDesignable
class QRCodeView: UIImageView {
#IBInspectable
var content:String = "test" {
didSet {
generateCode(content)
}
}
var qrIntrinsicSize: CGSize = CGSize(width: 100.0, height: 100.0)
override var intrinsicContentSize: CGSize {
return qrIntrinsicSize
}
lazy var filter = CIFilter(name: "CIQRCodeGenerator")
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
generateCode(content)
}
func setup() {
contentMode = .scaleToFill
// using DeviceKit from https://github.com/devicekit/DeviceKit
// replace with your lookup code that gets
// the device's ppi
let device = Device.current
guard let ppi = device.ppi else { return }
// convert to pixels-per-millimeter
let ppmm = CGFloat(ppi) / 25.4
// we want 15mm size
let mm15 = 15.0 * ppmm
// convert based on screen scale
let mmScale = mm15 / UIScreen.main.scale
// update our intrinsic size
self.qrIntrinsicSize = CGSize(width: mmScale, height: mmScale)
}
override func layoutSubviews() {
super.layoutSubviews()
generateCode(content)
}
func generateCode(_ string: String) {
guard let filter = filter,
let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
return
}
filter.setValue(data, forKey: "inputMessage")
guard let ciImage = filter.outputImage else {
return
}
let scX = bounds.width / ciImage.extent.size.width
let scY = bounds.height / ciImage.extent.size.height
let transform = CGAffineTransform(scaleX: scX, y: scY)
let scaled = UIImage(ciImage: ciImage.transformed(by: transform))
self.image = scaled
}
}

customize UIPageControl dots

I'm currently trying to customize the UIPageControl so it will fit my needs. However I seem to be having a problem when implementing some logic in the draw
What I want to do is to be able to user IBInspectable variables to draw out the UIPageControl however those seem to still be nil when the draw method is being called and when I try to implement my logic in the awakeFromNib for instance it won't work.
What I did so far is the following
class BoardingPager: UIPageControl {
#IBInspectable var size: CGSize!
#IBInspectable var borderColor: UIColor!
#IBInspectable var borderWidth: CGFloat! = 1
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.pageIndicatorTintColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
setDots()
}
func setDots() {
for i in (0..<self.numberOfPages) {
let dot = self.subviews[i]
if size != nil {
let dotFrame = CGRect(x: size.width/2, y: size.height/2, width: size.width, height: size.height)
dot.frame = dotFrame
}
if i != self.currentPage {
dot.layer.cornerRadius = dot.frame.size.height / 2
dot.layer.borderColor = borderColor.cgColor
dot.layer.borderWidth = borderWidth
}
}
}
}
Another problem I'm facing is that I want to remove/add a border when the current page changes.
I'm hoping someone will be able to help me out

Can you get a UITableView's intrinsic content size to update based on the number of rows shown if scrolling is disabled?

We have a portion of our UI which is a small list of labels with color swatches next to them. The design I'm taking over has six of these hard-coded in the layout even though the actual data is dynamic, meaning if we only need to show three, we have to explicitly hide three, which also throws off the balance of the page. Making matters worse is each one of those 'items' is actually made up of several sub-views so a screen with six hard-coded items has eighteen IBOutlets.
What I'm trying to do is to instead use a UITableView to represent this small portion of the screen, and since it won't scroll, I was wondering if you can use AutoLayout to configure the intrinsic content height of the UITableView to be based on the number of rows.
Currently I have a test page with a UITableView vertically constrained to the center, but without a height constraint because I am hoping to have the table's intrinsic content size reflect the visible rows. I have also disabled scrolling on the table. When I reload the table, I call updateConstraints. But the table still does not resize.
Note: We can't use a UIStackView (which would have been perfect for this) because we have to target iOS8 and that wasn't introduced until iOS9, hence this solution.
Has anyone been able to do something similar to our needs?
Ok, so unlike UITextView, it doesn't look like UITableView ever returns an intrinsic size based on the visible rows. But that's not that big a deal to implement via a subclass, especially if there's a single section, no headers or footers, and the rows are of a fixed height.
class AutoSizingUiTableView : UITableView
{
override func intrinsicContentSize() -> CGSize
{
let requiredHeight = rowCount * rowHeight
return CGSize(width: UIView.noIntrinsicMetric, height: CGFloat(requiredHeight))
}
}
I'll leave it up to the reader to figure out how to get their own rowCount. The same if you have variable heights, multiple sections, etc. You just need more logic.
By doing this, it works great with AutoLayout. I just wish it handled this automatically.
// Define this puppy:
class AutoTableView: UITableView {
override func layoutSubviews() {
super.layoutSubviews()
self.invalidateIntrinsicContentSize()
}
override var intrinsicContentSize: CGSize {
get {
var height:CGFloat = 0;
for s in 0..<self.numberOfSections {
let nRowsSection = self.numberOfRows(inSection: s)
for r in 0..<nRowsSection {
height += self.rectForRow(at: IndexPath(row: r, section: s)).size.height;
}
}
return CGSize(width: UIView.noIntrinsicMetric, height: height)
}
set {
}
}
}
and make it your class in IB.
obs: this is if your class is only cells and shit. if it has header, footer or some other thign, dunno. it'll not work. for my purposes it works
peace
This can be done, please see below for a very simple (and rough - rotation does not work properly!) example, which allows you to update the size of the table view by entering a number in the text field and resetting with a button.
import UIKit
class ViewController: UIViewController {
var tableViewController : FlexibleTableViewController!
var textView : UITextView!
var button : UIButton!
var count : Int! {
didSet {
self.refreshDataSource()
}
}
var dataSource : [Int]!
let rowHeight : CGFloat = 50
override func viewDidLoad() {
super.viewDidLoad()
// Configure
self.tableViewController = FlexibleTableViewController(style: UITableViewStyle.plain)
self.count = 10
self.tableViewController.tableView.backgroundColor = UIColor.red
self.textView = UITextView()
self.textView.textAlignment = NSTextAlignment.center
self.textView.textColor = UIColor.white
self.textView.backgroundColor = UIColor.blue
self.button = UIButton()
self.button.setTitle("Reset", for: UIControlState.normal)
self.button.setTitleColor(UIColor.white, for: UIControlState.normal)
self.button.backgroundColor = UIColor.red
self.button.addTarget(self, action: #selector(self.updateTable), for: UIControlEvents.touchUpInside)
self.layoutFrames()
// Assemble
self.view.addSubview(self.tableViewController.tableView)
self.view.addSubview(self.textView)
self.view.addSubview(self.button)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func refreshDataSource() -> Void {
if let _ = self.dataSource {
if !self.dataSource.isEmpty {
self.dataSource.removeAll()
}
}
else
{
self.dataSource = [Int]()
}
for count in 0..<self.count {
self.dataSource.append(count)
}
self.tableViewController.dataSource = self.dataSource
self.tableViewController.tableView.reloadData()
if let _ = self.view {
self.layoutFrames()
self.view.setNeedsDisplay()
}
}
func updateTable() -> Void {
guard let _ = self.textView.text else { return }
guard let validNumber = Int(self.textView.text!) else { return }
self.count = validNumber
}
func layoutFrames() -> Void {
if self.tableViewController.tableView != nil {
self.tableViewController.tableView.frame = CGRect(origin: CGPoint(x: self.view.frame.width / 2 - 100, y: 100), size: CGSize(width: 200, height: CGFloat(self.dataSource.count) * self.rowHeight))
NSLog("\(self.tableViewController.tableView.frame)")
}
if self.textView != nil {
self.textView.frame = CGRect(origin: CGPoint(x: 50, y: 100), size: CGSize(width: 100, height: 100))
}
if self.button != nil {
self.button.frame = CGRect(origin: CGPoint(x: 50, y: 150), size: CGSize(width: 100, height: 100))
}
}
}
class FlexibleTableViewController : UITableViewController {
var dataSource : [Int]!
override init(style: UITableViewStyle) {
super.init(style: style)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSource.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell()
cell.frame = CGRect(origin: CGPoint(x: 10, y: 5), size: CGSize(width: 180, height : 40))
cell.backgroundColor = UIColor.green
return cell
}
}
Whether it is a good idea or not, is, as has been pointed out, another question! Hope that helps!
Version from no_ripcord accounting for header and footer height
final // until proven otherwise
class IntrinsicallySizedTableView: UITableView {
override func layoutSubviews() {
super.layoutSubviews()
self.invalidateIntrinsicContentSize()
}
override var intrinsicContentSize: CGSize {
guard let dataSource = self.dataSource else {
return super.intrinsicContentSize
}
var height: CGFloat = (tableHeaderView?.intrinsicContentSize.height ?? 0)
+ contentInset.top + contentInset.bottom
if let footer = tableFooterView {
height += footer.intrinsicContentSize.height
}
let nsections = dataSource.numberOfSections?(in: self) ?? self.numberOfSections
for section in 0..<nsections {
let sectionheader = rectForHeader(inSection: section)
height += sectionheader.height
let sectionfooter = rectForFooter(inSection: section)
height += sectionfooter.height
let nRowsSection = self.numberOfRows(inSection: section)
for row in 0..<nRowsSection {
height += self.rectForRow(at: IndexPath(row: row, section: section)).size.height
}
}
return CGSize(width: UIView.noIntrinsicMetric, height: height)
}
}