-
Notifications
You must be signed in to change notification settings - Fork 143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add automatic sizeThatFits computation for views #216
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Salut Antoine 🙂
I like the general idea of your PR.
Global variable Pin.autoSizingInProgress
The only thing that bothers me is Pin.autoSizingInProgress
. I know that all layouts related code should be executed from the main thread, so, a well-programmed app should not have 2 views that are layouted simultaneously. So having a global variable that contains that state shouldn't be an issue. But still, it's a global variable.
I'm not sure what could be a nicer solution thought, or if there is such a solution. But here is some thought on it (maybe this will trigger other ideas on your side 🤞).
Solution 1: Add a layout context
Suppose we add a method pin(_ context: LayoutContext?)
and we add a context parameter to the layoutClosure
, this context could then be passed to all pin
calls. Ex:
override func sizeThatFits(_ size: CGSize) -> CGSize {
return autoSizeThatFits(size) { (ctx: LayoutContext) in
layout(ctx) }
}
private func layout(_ ctx: LayoutContext?) {
subview.pin(ctx).top().left(10).width(200);
}
Cons:
- A little verbose and not as nice.
- It's easy to forget to call
pin(_ context: LayoutContext?)
instead of.pin.
Solution 2: Check view's parents
Check in the view hierarchy if there is a parent view with an autoSizingRect
value, in that case, autoSizing is in progress. For this to work, we need to set to nil
the property autoSizingRect
when leaving autoSizeThatFits(...)
.
Cons:
- Need to scans all view's parents to detect that there is no autoSizing in progress 😞
Solution 3: Global variable
Keep the global variable 😕
Experimental feature
In all cases I would keep that feature as experimental, i.e. we don't document immediately until you have played with the feature and you are happy with the result. During that time, you could note anything that would be useful to eventually document.
Non main thread calls warnings
I would also add a warning when .pin
is called from a thread other than the main thread (https://github.com/layoutBox/PinLayout#pinlayouts-warnings).
Ex:
This warning could be disabled Pin.activeWarnings.mainThread
.
...
I will continue to think about this feauture, so I may add other comments later
Example/PinLayoutSample/UI/Examples/AdjustToContainer/Subviews/ChoiceSelectorView.swift
Outdated
Show resolved
Hide resolved
Example/PinLayoutSample/UI/Examples/AdjustToContainer/Subviews/ChoiceSelectorView.swift
Outdated
Show resolved
Hide resolved
} | ||
} | ||
|
||
public func autoSizeThatFits(_ size: CGSize, layoutClosure: () -> Void) -> CGSize { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really would like to extract that function out of the UIView conformance extension but the only thing that prevent me to do so is the call to let adjustedRect = Coordinates<View>.adjustRectToDisplayScale(rect)
. I think it would make sense to add a displayScale
property on the Layoutable
protocol and perform that transformation before calling setRect
. We really want the resulting rect to be the same in both layout and auto sizing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have perform the changes to do this but I believe it would make more sense in a separate PR as it also fix an issue in the current production code.
I agree that a global feature flag is probably not the most elegant solution but at the same time it's the less invasive way to implement it. I started trying to implement it creating a Pin object for a different object type acting as a view proxy but the code was overly complex and I ended up having the same need for a state variable. Solution 1 Solution 2 Solution 3 The API is a bit similar to |
I agree @antoinelamy, that your solution is probably the best, even if there is one flaw, its the one that has a minimal impact on code |
From what I see there is already such warning in
|
Should be good to go now @lucdion, anything else comes to mind? |
Two more things:
|
@lucdion I thought you preferred to silent release this feature, my mistake. I added a proper sample that fetches a random text and a random sized image. That content is then layout in a container that uses autosizing to determine its proper size. I also included setting a scroll view content size to illustrate the fact that we should not call non PinLayout code in the layout closure passed to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ready to go 🎉
Thanks @antoinelamy for this nice addition
Disclaimer
This is a preliminary PR for discussion purpose. The implemented functionality has barely been tested and is not guaranteed to be bug free.
Motivation
Implementing
sizeThatFits(_ size: CGSize)
as part of the manual layout process has always been cumbersome. You always end up writing the same code twice, a first time for the layout and the second time for sizing. Using PinLayout to compute the resulting size like showcased in theAdjustToContainer
exemple is not recommended either because the view coordinates are modified during sizing. ThesizeThatFits
method documentation state clearly:Proposal
Build an autosizing mechanism on top of PinLayout without modifying the view's coordinates in the process.
AutoSizeCalculable
interface that defines autosizing related functions and properties.Pin.autoSizingInProgress
) is also needed for the layout system to know if it should compute an additional rect including the margins.sizeThatFits(_ size: CGSize)
using automatic sizing requires the following:layoutSubviews()
to be sure things like setting the content size on a scroll view are not executed during the sizing process. In the provided exemple, that function would belayout()
.sizeThatFits
implementation is as simple as callingreturn autoSizeThatFits(size) { layout() }
and even takes into account the outer margins (ie: the bottom margin applied to the last view on y axis)Limitations
Layoutable
because of the need to define stored properties.PinLayout
only, otherwise the views that uses another layout system would be ignored in the resulting computed size.Discussion
I would very much like feedback on this PR discussing the concept and exposing the potential flaws if any. I think that if we can get this thing to work properly in every situation it would be a great addition to PinLayout.