Skip to content
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

CustomSplitter disappears when constraints is set #30

Closed
fenixkim opened this issue Nov 29, 2023 · 4 comments · Fixed by #34
Closed

CustomSplitter disappears when constraints is set #30

fenixkim opened this issue Nov 29, 2023 · 4 comments · Fixed by #34

Comments

@fenixkim
Copy link

When I add .constraints(minPFraction:minSFraction) the CustomSplitter disappears. Which should not happen.

CleanShot 2023-11-29 at 10 07 59

Sample code:

let layout0 = demo.holders[0].layout
let hide0 = demo.holders[0].hide
let styling = SplitStyling(visibleThickness: 20)
Split(
    primary: { Color.green },
    secondary: { Color.red }
)
.splitter { DemoSplitter(layout: layout0, hide: hide0, styling: styling) }
.layout(layout0)
.hide(hide0)
.constraints(minPFraction: 0.2, minSFraction: 0.2, dragToHideS: true)

Is there something I am doing wrong or how can I solve it?

Thanks in advance

@stevengharris
Copy link
Owner

This behavior is by design. The reason is explained in the README:

One thing to note is that if you specify minPFraction or minSFraction, then when you hide a
side that has its minimum fraction specified, you won't be able to drag it out from its hidden
state. Why? Because it doesn't make sense to be able to drag from the hidden side when
you never could have dragged it to that location to begin with because of the constraint.
As soon as you tried to drag it, the splitter would snap to an allowed position, which is also
jarring to users. To make sure there is no visual confusion about whether a splitter can be
dragged, the splitter will not be shown at all when it is not draggable. Again: a splitter will
be non-draggable when a side is hidden and the corresponding minPFraction or
minSFraction is specified.

It perhaps seems like an unnecessary restriction, but this issue:

As soon as you tried to drag it, the splitter would snap to an allowed position

is pretty much baked into the onChanged section of the drag method in Split:

private func drag(in size: CGSize) -> some Gesture {
    return DragGesture()
        .onChanged { gesture in
            unhide(in: size)    // Unhide if the splitter is hidden, but resetting constrainedFraction first
            let fraction = fraction(for: gesture, in: size)
            constrainedFraction = fraction.constrained
            fullFraction = fraction.full
            splitter.styling.previewHide = !isDraggable() || sideToHide() != nil
            onDrag?(constrainedFraction)
            previousPosition = layout.isHorizontal ? constrainedFraction * size.width : constrainedFraction * size.height
        }
        .onEnded { gesture in
            ...
        }
}

While you're dragging, we need to keep the fraction within the constraints, so if the splitter is visible but outside of the constraints, then the first execution of the onChanged block at drag has to put the splitter back within the constraints, which is a big jump and disturbing for users (or at least for me as a user!). This is why the behavior of hiding the splitter is hardcoded. The hardcoding is in the last line of the private init method:

if dragToHideP || dragToHideS { self.splitter.styling.hideSplitter = true }

The jump-on-drag behavior used to be easy to see by commenting this line out, but doing that now leaves a visibleWidth gap for the Splitter but it still isn't draggable. However, if you find a way to still show the Splitter, I don't think you will find the jump-to-constraint behavior to be acceptable as soon as you start to drag it out. You might then be ambitious-enough to try to fix the drag loop for the special case of the Splitter being outside of the constraints when drag starts, but I think that would be difficult. For example, you have to differentiate between this "I started outside of the constraints" case and the normal "Keep me within the constraints" case at each drag event. Then what do you do when dragging ends outside of the constraints, which normally it never would? I'm concerned it would become a deep rat hole quickly, with implications on things like constraint priority that won't be obvious.

@stevengharris
Copy link
Owner

stevengharris commented Nov 29, 2023

Looking into this, I see there is a bug which unfortunately doesn't help on your issue. If you specify constraints without dragToHideS: true, then when the secondary side is hidden, the secondary side width is inset by the custom splitter visibleWidth. I'll be fixing this in func spacing() as:

private func spacing() -> CGFloat {
    let styling = splitter.styling
    if styling.previewHide {
        return 0
    } else if hide.side != nil && styling.hideSplitter {
        return 0
    } else if ((hide.side?.isPrimary ?? false) && minPFraction != nil) || ((hide.side?.isSecondary ?? false) && minSFraction != nil) {
        return 0
    } else {
        return styling.visibleThickness
    }
}

This fixes the gap I noted above in "The jump-on-drag behavior used to be easy to see by commenting this line out, but doing that now leaves a visibleWidth gap for the Splitter but it still isn't draggable. "

If you want to override the Splitter becoming completely hidden, you can return true from isDraggable. It's going to look funky, but it will give you a feel for what the ugly "jump to the constraints" behavior looks like.

Thanks for bringing the original issue up. Hope the explanation was useful.

@fenixkim
Copy link
Author

First of all thank you for the detailed response. It is hard to understand and shows the amount of validations you had to deal with in order to make the Splitter's crawl behavior feel natural.

This library is very helpful and from my point of view it is important that it is close to the usual behavior as shown in the following image.

CleanShot 2023-11-30 at 08 39 05

If you can achieve exactly the behavior shown here natively in this library that would be great. Otherwise I prefer to remove the constraints (minSFraction for my case).

Tell me what you think?

@stevengharris
Copy link
Owner

Oh, I see, and I think you are right. My goal in implementing the drag-to-hide feature was to support the native behavior seen in Xcode and elsewhere. I hope to have some time over the next week to do this fix. Your video made me realize that the "previewHide" toggle that I did for the drag-to-hide feature works just as well to fix the problem I was having with the "jump to constraints" issue before, so I'm hoping it won't be too difficult.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants