Nested UITableView dynamic height - swift

I need to have a nested UITableView in a tableView cell (actually, two tables in one cell) to show different lists with dynamic content (so, I need dynamic heights). My nested tables will not have scrolling—I need them just to order elements of different kinds, like texts, pictures, fields etc. To be more clear—the first level is the level of operations and every operation can have a variable amount of instructions and actions. Instructions and actions should be placed side by side and the operation cell should have the size of the tallest table.
There are no problems in nesting tables, but I faced a problem with the auto layout. I’ve tried everything that I could find, but with no success.
I tried height constraints for nested table views, which I update on the operation cell creation from tableview.contentsize.hight, but it seems that contentsize returns height based on the estimated size of every row, but not the actual size.
I tried to rewrite intrinsic content size of nested tables:
UITableView {
override var contentSize:CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
}
}
Nothing works properly. Any ideas how it could be solved?
Thank you in advance.

Set Inner tableview my custom class AGTableView and height Constraint both are required,
this class set contantSize same table view height Constraint.
Check out Github AutoHeightIncrementTableViewDemo
class AGTableView: UITableView {
fileprivate var heightConstraint: NSLayoutConstraint!
override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
self.associateConstraints()
defaultInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.associateConstraints()
defaultInit()
}
func defaultInit(){
self.keyboardDismissMode = .onDrag
self.showsVerticalScrollIndicator = false
self.showsHorizontalScrollIndicator = false
self.tableFooterView = UIView(frame: .zero)
self.tableHeaderView = UIView(frame: .zero)
self.sectionFooterHeight = 0
self.sectionHeaderHeight = 0
}
override open func layoutSubviews() {
super.layoutSubviews()
if self.heightConstraint != nil {
self.heightConstraint.constant = self.contentSize.height
}
else{
print("Set a heightConstraint to set cocontentSize with same")
}
}
func associateConstraints() {
// iterate through all text view's constraints and identify
// height
for constraint: NSLayoutConstraint in constraints {
if constraint.firstAttribute == .height {
if constraint.relation == .equal {
heightConstraint = constraint
}
}
}
}
}
NOTE: Also set a estimatedRowHeight
self.rowHeight = UITableViewAutomaticDimension
self.estimatedRowHeight = height

Related

Can not update scroll View height for a content view.bounds.height

I have a code that contains a lot of different views and so on.. and I need to set ScrollView on it, then using func update scrollview height depends on contentView.frame.height
My code:
private lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.automaticallyAdjustsScrollIndicatorInsets = false
scrollView.contentInsetAdjustmentBehavior = .never
return scrollView
}()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
configureUI()
}
There is a configureUI func where I set all views
private func configureUI() {
view.addSubview(scrollView)
scrollView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
scrollView.addSubview(contentView)
contentView.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.width.equalToSuperview()
make.height.equalTo(1400) ?????? do I need it?
}
contentView.addSubview(rocketImage)
rocketImage.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
}
contentView.addSubview(backgroundForInfo)
backgroundForInfo.snp.makeConstraints { make in
make.top.equalTo(rocketImage.snp.bottom).inset(50)
make.left.right.equalToSuperview()
make.bottom.equalToSuperview()
}
backgroundForInfo.addSubview(stackView)
stackView.snp.makeConstraints { make in
make.top.equalToSuperview().inset(40)
make.leading.trailing.equalToSuperview()
}
backgroundForInfo.addSubview(launchButton)
launchButton.snp.makeConstraints { make in
make.top.equalTo(stackView.snp.bottom).offset(20)
make.leading.trailing.equalToSuperview().inset(20)
make.height.equalTo(40)
}
view.addSubview(pageControl)
pageControl.snp.makeConstraints { make in
make.bottom.equalToSuperview()
make.leading.trailing.equalToSuperview()
make.height.equalTo(80)
}
updateScrollViewHeight()
}
Func for checking height of contentView
private func updateScrollViewHeight() {
view.layoutIfNeeded()
let allViewsHeight = rocketImage.bounds.height + backgroundForInfo.bounds.height
scrollView.contentSize = CGSize(width: view.bounds.width, height: allViewsHeight)
}
If I set contentView Height 1400 I have empty spaces at the bottom of scrollView, if I set <1000 I do not see a lot of content.
So I use a func that calucalute scroll height
of course I can hardcode 1200 and everything will be all right, but what if I will not have some of "Stage" parameters (you can see it on screenshot)

UICollectionViewFlowLayout with multiple columns and dynamic cell sizing

My goal is to implement a collection that looks like so:
Instagram's discover tab
I can achieve a similar result using UICollectionViewCompositionalLayout but I am constrained to iOS versions lower than 13.0, which makes this a no go.
I have been testing UICollectionViewFlowLayout and I can achieve multiple columns using collectionView(_:layout:sizeForItemAt:), but I can't find a way to dynamically calculate the height of the cells, which can be either an image, a label, etc.
Thanks
You need to subclass UICollectionViewFlowLayout which will provide you an opportunity to all the modifications to cell frames and more.
import UIKit
class InstagramCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// These attributes have all the information about how
// collectionView's default layout will layout your cells
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
guard let collectionView = self.collectionView else { return nil }
var newAttributes: [UICollectionViewLayoutAttributes] = []
let layout = collectionView.collectionViewLayout
for attribute in attributes {
if let cellAttribute = layout.layoutAttributesForItem(at: attribute.indexPath) {
// Decide which indexPaths' cells should appear bigger
if attribute.indexPath.item % 2 == 0 {
// whatever modifications you want for big cells
cellAttribute.frame.size = CGSize(width: 200, height: 200)
}
else {
// whatever modifications you want for small cells
cellAttribute.frame.size = CGSize(width: 70, height: 70)
}
// Since you are modifying default layout and making some cells bigger
// You will need to accoun for extra height in your calculations
// I am not doing that here
newAttributes.append(cellAttribute)
}
}
return newAttributes
}
}
Usage
let igFlowLayout = InstagramCollectionViewFlowLayout()
collectionView.collectionViewLayout = igFlowLayout
Notes
This is just an idea of how you are supposed to create a subclass to customize cell frames. You will need to add a lot more to this according to your own implementation.
Subclassing UICollectionViewLayout also means that now you are fully responsible for telling collectionView how big it's collectionViewContentSize should be for all your data.
Reference
Customizing Collection View Layouts has all the information you need to get started on this. They explain how to achieve a similar layout in their example.

How to make scrollview scroll separately to its content height in swift

I have view hierarchy like below in storyboard
here for content main constrains top = 0, leading = 0, trailing = 0, bottom = 0
here for scrollview constrains top = 0, leading = 0, trailing = 0, bottom = 0
here for View constrains top = 0, leading = 0, trailing = 0, bottom = 0
for ContentView constrains top = 0, leading = 0, trailing = 0
for TblReview constrains top = 0, leading = 0, trailing = 0, bottom = 20
for Productcollectionview constrains top = 0, leading = 0, trailing = 0, bottom = 20, height = 400
and i have Productcollectionview height outlet like below in swift file
and i don't want collectionview separate scrolling.. i want total view to scroll according to collectionview cells.. so for that i have written below code but with this code contentView and tblReview also scrolling upto productioncollectionview height i need contentView should scroll upto its content height and tblReview should scroll upto its rows
how to make scrolling separately to its height.
please help me to solve this issue
#IBOutlet weak var productCollHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
productCollectionView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
let collectionView = object as? UICollectionView
if collectionView == self.productCollectionView{
if(keyPath == "contentSize"){
if let newvalue = change?[.newKey]
{
let newsize = newvalue as! CGSize
self.productCollHeight.constant = newsize.height
}
}
}
}
#IBAction func aboutCompany(_ sender: UIButton){
self.productCollectionView.isHidden = true
self.tblReview.isHidden = true
self.contentView.isHidden = false
}
#IBAction func review(_ sender: UIButton){
self.productCollectionView.isHidden = true
self.tblReview.isHidden = false
self.contentView.isHidden = true
}
#IBAction func productOfSeller(_ sender: UIButton){
self.productCollectionView.isHidden = false
self.tblReview.isHidden = true
self.contentView.isHidden = true
}
and i am hiding and showing tblReview and contentView according to need
with the above code tblReview and contentView are also scrolling upto productCollectionView height.. please help me to solve this error
EDIT: share this seller is out of scrollview so here contentView height is not not so long but if i scroll the contentView also scrolling too long like productioncollectionview
this is contentView which is scrolling too long like productioncollectionview
if your all constraint are proper than just use it like this you don't need to count every time. UICollectionView has intrinsicContentSize it will count it properly.
final class ContentSizedCollectionView: UICollectionView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
put that code in controller or wherever you wan to put and assign ContentSizedCollectionView class to your collectionview.
NOTE: you can also use it with UITableView after creating for UITableView.

changing the multiplier value of a constraint

I have a menu bar which sits at the top of a CollectionViewController with several different titles about 30 pixels high
. Each title has a small bar underneath, indicating which page/item the user is on, similar to the Youtube app.
class MenuBar : UIView {
var menuSectionTitles = ["Title1","Title2", "Title3","Title4"]
var numberOfSectionsInHorizontalBar : CGFloat?
var horizontalBarLeftAnchorConstraint: NSLayoutConstraint?
var horizontalBarWidthAnchorConstraint: NSLayoutConstraint?
override init(frame: CGRect) {
super.init(frame: frame)
setupHorizontalBar()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupHorizontalBar() {
let horizontalBarView = UIView()
horizontalBarView.backgroundColor = UIColor.rgb(10, green: 150, blue: 255)
horizontalBarView.translatesAutoresizingMaskIntoConstraints = false
addSubview(horizontalBarView)
horizontalBarLeftAnchorConstraint = horizontalBarView.leftAnchor.constraint(equalTo: self.leftAnchor)
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalBarView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
// Set the multiplier/width of the horizontal bar to indicate which page the user is on
horizontalBarWidthAnchorConstraint = horizontalBarView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1 / 4)
horizontalBarWidthAnchorConstraint?.isActive = true
horizontalBarView.heightAnchor.constraint(equalToConstant: 3).isActive = true
}
}
The multiplier value for horizontalBarWidthAnchorConstraint is hard coded in at a value of 1/4
I want to reuse the view multiple times across different view controllers, therefore have attempted to change the multiplier of the horizontalBarWidthAnchorConstraint like so:
class ViewController: UICollectionViewController {
lazy var menuBar : MenuBar = {
let mb = OptionsBar()
mb.menuOptions = ["Title1", "Title2", "Title3"]
mb.translatesAutoresizingMaskIntoConstraints = false
mb.horizontalBarWidthAnchorConstraint?.constant = 1/3
return mb
}()
}
But this method does not work. I can not find a way to change the multiplier value from inside the ViewController.
In the code here you are changing the constant of the constraint, not the multiplier. Is that correct?
Also note, the multiplier of a constraint is read only. You cannot change it. If you want to change the multiplier of a constraint applying to a view then the only to do it is to add a new constraint with the new multiplier and remove the old constraint.
The only part of a constraint that is read/write is the constant property.

Custom Activity Indicator with Swift

I am trying to create a custom activity indicator like one of these. There seems to be many approaches to animating the image, so I'm trying to figure out the best approach.
I found this tutorial with Photoshop + Aftereffects animation loop, but as the comment points out, it seems overly complex (and I don't own After Effects).
tldr: how can I take my existing image and animate it as a activity indicator (using either rotating/spinning animation or looping through an animation)
It's been a while since you posted this question, so I'm not sure if you've found the answer you're looking for.
You are right there are many ways to do what you are asking, and I don't think there's a "right way". I would make a UIView object which have a start and a stop method as the only public methods. I would also make it a singleton, so it's a shared object and not possible to put multiple instances on a UIViewController (imagine the mess it could make).
You can add a dataSource protocol which returns a UIViewController so the custom activity indicator could add itself to it's parent.
In the start method I would do whatever animations is needed (transform, rotate, GIF, etc.).
Here's an example:
import UIKit
protocol CustomActivityIndicatorDataSource {
func activityIndicatorParent() -> UIViewController
}
class CustomActivityIndicator: UIView {
private var activityIndicatorImageView: UIImageView
var dataSource: CustomActivityIndicatorDataSource? {
didSet {
self.setupView()
}
}
// Singleton
statice let sharedInstance = CustomActivityIndicator()
// MARK:- Initialiser
private init() {
var frame = CGRectMake(0,0,200,200)
self.activityIndicatorImageView = UIImageView(frame: frame)
self.activityIndicatorImageView.image = UIImage(named: "myImage")
super.init(frame: frame)
self.addSubview(self.activityIndicatorImageView)
self.hidden = true
}
internal required init?(coder aDecoder: NSCoder) {
self.activityIndicatorImageView = UIImageView(frame: CGRectMake(0, 0, 200, 200))
self.activityIndicatorImageView = UIImage(named: "myImage")
super.init(coder: aDecoder)
}
// MARK:- Helper methods
private func setupView() {
if self.dataSource != nil {
self.removeFromSuperview()
self.dataSource!.activityIndicatorParent().addSubview(self)
self.center = self.dataSource!.activityIndicatorParent().center
}
}
// MARK:- Animation methods
/**
Set active to true, and starts the animation
*/
func startAnimation() {
// A custom class which does thread handling. But you can use the dispatch methods.
ThreadController.performBlockOnMainQueue{
self.active = true
self.myAnimation
self.hidden = false
}
}
/**
This will set the 'active' boolean to false.
Remeber to remove the view from the superview manually
*/
func stopAnimation() {
ThreadController.performBlockOnMainQueue{
self.active = false
self.hidden = true
}
}
}
Mind that this is not fully tested and may need some tweaks to work 100%, but this is how I basically would handle it.