Skip to content
This repository has been archived by the owner on Dec 22, 2023. It is now read-only.

Question: Is automatic height for children supported? #172

Closed
zocario opened this issue Mar 26, 2020 · 14 comments
Closed

Question: Is automatic height for children supported? #172

zocario opened this issue Mar 26, 2020 · 14 comments
Labels
question Further information is requested

Comments

@zocario
Copy link

zocario commented Mar 26, 2020

Hello and thanks for this library :)

We're trying to take advantage of child view controllers to split a view controller that is now really too complex. Here is how we want to organize it after refactoring (at the moment this is one UITableViewController subclass):

image

  1. One view controller for the header
  2. One view controller for our list of items that are linked to core data using an NSFetchedResultsController
  3. A fixed view controller for a toolbar allowing to enter text and create content

I've just started to play with your library and I encountered a problem : our header has a dynamic height, which is set automatically with auto layout constraints. I've succeed to set fixed height for this container using the height parameter of addChild but with only auto layout constraints I couldn't. Also I encountered a case where I could scroll in the tableview and the main scroll view wasn't scrolling..

So I would like to know if this is supported to have height set using auto layout? Regarding #73 I have the feeling it's not.

Regarding your experience in child view controller, is it a good pattern to split this controller in three view controllers in that way? Maybe it's just easier to use the table view header to add the header controller?

I've included a sample project to quickly show the problem I'm having.
Test family.zip

Thanks for your help!

@zocario zocario changed the title Question: Is automatic size for children supported? Question: Is automatic height for children supported? Mar 26, 2020
@zenangst
Copy link
Owner

@zocario hey mate, you should be able to use view controllers that use auto layout, Family simply listens to frame changes on its children and tries to size accordingly. The thing that you need to take into account is how you implement the children for this to work. I'll take a look at your demo project to see if I can give you any more concert pointers, cheers! :)

@zenangst
Copy link
Owner

@zocario Is this what you want to achieve?

Simulator Screen Shot - iPhone 11 - 2020-03-26 at 18 06 30

I modified the HeaderViewController to be self-sizing based on the size of the subview.

class HeaderViewController: UIViewController {
    let containerView = UIView()
    let subview = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        containerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.backgroundColor = .gray

        subview.backgroundColor = .red
        subview.translatesAutoresizingMaskIntoConstraints = false
        containerView.addSubview(subview)

        subview.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
        subview.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
        subview.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
        #warning("This constraints is removed as impossible to statisfies")
        subview.heightAnchor.constraint(equalToConstant: 200).isActive = true
        self.view.addSubview(containerView)

        containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        containerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        view.frame.size.height = subview.frame.size.height
    }
}

@zenangst
Copy link
Owner

zenangst commented Mar 26, 2020

@zocario here is another example if you want to toggle the height by tapping on the view.
Think about the view.frame.height as the contentSize for your UIViewController.
The outmost container will provide the content size to Family so that it know how big it should draw the element.

class HeaderViewController: UIViewController {
    let containerView = UIView()
    let subview = UIView()
    var heightConstraint: NSLayoutConstraint?

    override func viewDidLoad() {
        super.viewDidLoad()

        containerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.backgroundColor = .gray

        subview.backgroundColor = .red
        subview.translatesAutoresizingMaskIntoConstraints = false
        containerView.addSubview(subview)

        subview.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
        subview.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
        subview.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true

        heightConstraint = subview.heightAnchor.constraint(equalToConstant: 200)

        #warning("This constraints is removed as impossible to statisfies")
        heightConstraint?.isActive = true
        self.view.addSubview(containerView)

        containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        containerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
        subview.addGestureRecognizer(tapGesture)
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        view.frame.size.height = subview.frame.size.height
    }

    @objc func toggle() {
        if heightConstraint?.constant == 200 {
            heightConstraint?.constant = 100
        } else {
            heightConstraint?.constant = 200
        }

        UIView.animate(withDuration: 0.25, delay: 0.0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
            self.view.layoutIfNeeded()
            self.view.frame.size.height = self.subview.frame.size.height
        }, completion: nil)
    }
}

@zenangst
Copy link
Owner

zenangst commented Mar 26, 2020

And to answer your original question, the current outline that you presented is probably a better fit to implement as a UITableViewController. Mainly because it seems like the perfect fit for that use case. Header, Content, Footer. You basically get all three from UIKit. A big plus is also that for other devs, it is probably easier to run with vanilla as most devs have done this boogie countless time in the past.

The pros with doing child view controller is that you decouple yourself from a reuse/dequeing scheme. You can basically feed it your view models directly and guarantee that you have an instance of something instead of going through the hoops of using the delegate, reload, cell for item at index paths methods etc.

I'd roll with doing the basics and when you need more, you simply refactor it using something that can fulfill those requirements.

Hope that helps! Feel free to followup if you have anything else that you are wondering about :)
I'm here to help ❤️

@zocario
Copy link
Author

zocario commented Mar 26, 2020

Hello @zenangst thanks for your detailed answer that was really fast!

I'll give it an other try regarding your answers and maybe re-consider using the table view header :) The footer isn't scrolling with the tableview that's why I thought about this three view controllers setup.

@zenangst
Copy link
Owner

Should you be able to set the footer as sticky if you want it to follow the scrolling?
Either that or add a bit of content inset at the bottom and just have the footer live on top of the table view?

There are multiple ways to skin a cat :)

@zocario
Copy link
Author

zocario commented Mar 30, 2020

Yes effectively there is a lots of possible ways, maybe too much :)

I did a second version of my test that is a little bit closer to our view implementation, which uses a vertical stack view with labels, images. In that case I'm still facing a problem with the height of my header.

Simulator Screen Shot - iPhone 11 Pro - 2020-03-30 at 11 14 22

To be honest I'm not sure to understand why I need to set the height of my view in viewDidLayoutSubviews as to me we have all info to have self sizing view with auto layout. Should I compute myself the height of my header to have it respecting the good height?

Updated version of my sample with a vertical stack view:
Test family.zip

Thanks!

@zenangst
Copy link
Owner

@zocario I'll have a second look at this when life allows but basically, the view of a view controller is not automatically self-sizing. Hence you having to change the size of that view for it to work, to get rid of having to set a manual size of your child view controller, you could use a custom view as the view controllers view by implementing loadView, that should eliminate the need to implement viewDidLayoutSubviews.

But like I said, I'm gonna look at your example when life allows and get back to you with more details.

@zenangst
Copy link
Owner

zenangst commented Apr 2, 2020

@zocario the easiest way to make a self-sizing view controller is to switch out the view of the view controller, then you don't need the viewDidLayoutSubviews() implementation.

Note that this is not something that is unique for Family. For a view controller to be self-sizing, it needs an implementation that uses some for of intrinsicContentSize which a regular UIView does not have. UIStackView however is perfect for making easy to use self-sizing views.

If you take a look at this example:

class HeaderViewController: UIViewController {
    let customView = UIStackView()

    override func loadView() {
        self.view = customView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let label = UILabel()
        label.numberOfLines = 0
        label.textColor = .white
        label.text = "SALU TAOJ EAOJ \naAK LK ALZK F\nAOK ZOAK F"

        let label2 = UILabel()
        label2.textColor = .red
        label2.numberOfLines = 0
        label2.text = "SALU TAOJ EAOJ \naAK LK ALZK F\nAOK ZOAK F"

        customView.addArrangedSubview(label)
        customView.addArrangedSubview(label2)
        customView.axis = .vertical
        customView.backgroundColor = .red
        customView.translatesAutoresizingMaskIntoConstraints = false
    }
}

It will render something like this:

image

I hope this helps!

@zenangst
Copy link
Owner

Hey @zocario did you manage to get anywhere with this?

@zenangst
Copy link
Owner

ping @zocario

@zocario
Copy link
Author

zocario commented Aug 12, 2020

Hello @zenangst I'm really sorry I disabled email notifications and missed your last comments 🙈 For this specific view I finally used a table view as it was making more sense. But it looks like you're right assigning the view in loadView fixed the issue! Thanks for your help, I'm starting a more complex view where I'll really need this so I'll be able to validate this again :)

@zocario
Copy link
Author

zocario commented Aug 12, 2020

I mark the issue as solved 🙏

@zocario zocario closed this as completed Aug 12, 2020
@zenangst
Copy link
Owner

@zocario no worries mate, cheers! 😎

@zenangst zenangst added the question Further information is requested label Oct 7, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants