I am creating a set of lists in a horizontal scrollview for a MacOS App. Basically the lists would appear side by side.
var body: some View {
ScrollView (.horizontal, showsIndicators: true){
ForEach(self.dates, id: \.self) { day in
ScrollView {
Text("\day.date")
Text("Other content")
}
}
}
}
The individual (vertical) scrollviews make it not possible for the app to register horizontal scrolling. When having the pointer on the individual lists, the only option is vertical scrolling. When putting the pointer in between the lists (outside - but within the horizontal scrollview) the horizontal scrolling works.
Any ideas on how to enable horizontal scrolling while hovering over a vertical scrollview?
Other option would be to make horizontal scrollbar indicator visible at all times, but currently it hides itself when not moved. Any ideas on how to achieve that?
Related
I have a very simple view that only shows a Text, a Shape, and a Button stacked vertically in a ScrollView. The Shape is a Capsule and is conditionally shown only when showCapsule is true.
struct ContentView: View {
#State var showCapsule = true
var body: some View {
ScrollView {
VStack(spacing: 16) {
Text("Why, oh why? 😩")
.font(.headline)
if showCapsule {
Capsule()
.frame(maxWidth: .infinity)
.frame(height: 100)
.foregroundColor(.blue)
}
Button {
showCapsule.toggle()
} label: {
Text(showCapsule ? "Hide" : "Show")
}
.buttonStyle(.bordered)
}
.frame(maxWidth: .infinity)
.padding()
}
.animation(.default, value: showCapsule)
}
}
Observed and expected animation
I want to animate the appearance and disappearance of the Capsule, but the result is totally not what I want. While the Capsule fades out (which is okay), the button is animated in two different ways simultaneously:
Its background shape (the grey rounded rectangle) move from the old to its new position.
Its text fades out at its old position and fades in at its new position.
Of course, (2) is not what I want. Instead, I want the button to move as a unit: The entire thing should move from its old to its new position while the text is faded inside of it.
The broader picture
This is a minimal example for a broader question: How do I animate changes to a view that is semantically the same but value-wise different?
In the Button initializer, I use a ternary operator to conditionally pass a different string to its Text label:
Button {
showCapsule.toggle()
} label: {
Text(showCapsule ? "Hide" : "Show")
}
Text("Hide") is a different value than Text("Show"), so I guess that's why SwiftUI can't identify them and doesn't "understand" that it should animate them in place. I observed the same behavior with custom views with let constants. Is there a way to make SwiftUI treat such views – and especially this Button – as a unit and animate them correctly?
Note
I'm not looking for a workaround like using two Text fields (or Buttons) and show/hide them by setting their opacity accordingly. Rather looking for a general solution for this kind of problem that solves the identity problem rather than patching its symptoms.
You can fix this by adding .drawingGroup modifier to your button:
Button(showCapsule ? "Hide" : "Show") {
showCapsule.toggle()
}
.drawingGroup()
.buttonStyle(.bordered)
Alternatively, you could have two buttons that are shown and hidden:
ZStack {
Button("Hide") {
showCapsule.toggle()
}
.buttonStyle(.bordered)
.opacity(showCapsule ? 1 : 0)
Button("Show in a very long button") {
showCapsule.toggle()
}
.buttonStyle(.bordered)
.opacity(showCapsule ? 0 : 1)
}
I found a weird behavior when testing TextField with SwiftUI on macOS, that does not exist on iOS.
Basically when a TextField has a fixed width and contains more text than it can display (aka it has an overflow), on iOS the user can move the cursor on the left/right (thus "scrolling" the text horizontally except there's no scrollbar) to see any portion of the text they want to display.
However on macOS, the exact same code doesn't allow the user to "scroll" horizontally and you can only see the first few characters of what you typed, no matter where your cursor is.
Here is a minimal code showing this behavior and a screen record of how it (doesn't) work:
import SwiftUI
struct TestView: View {
#State var value = ""
var body: some View {
VStack {
TextField("", text: $value)
// .fixedSize()
.frame(width: 150)
// .fixedSize()
Text(value)
}
.frame(minWidth: 200, minHeight: 100)
}
}
https://streamable.com/xj4sx8
So my question is: how can I fix my TextField width without having that kind of odd UI bug?
I want to make a scrolling app with a gradient background. As user scrolls - background color changes.
For example, the bottom is black and the top is white I would specify the height of a VStack for 8000, and for this height, as the user scrolls the screen he will see the color change.
I didn't find any solution. Tried making LinearGradient for VStack and Rectangle figures for its full height, but it only covers the phone screen size (can't fill all 8000 height points). So if I scroll upwards, the gradient is lost and it only displays the black screen.
Thank you]1
You can achieve this using a rectangle within a ZStack:
struct ContentView: View {
var body: some View {
ScrollView {
ZStack(alignment: .top) {
// Example width and height. Width should be the width of the device
Rectangle().frame(width: 10000, height: 2000).foregroundColor(.clear).background(LinearGradient(gradient: Gradient(colors: [.white, .black]), startPoint: .top, endPoint: .bottom))
// Add your actual content here:
Text("Yeet")
}
}
}
}
I'm trying to animate a constraint but it seems to be effecting the entire view. Here is the code that I am using to animate the constraint.
#IBOutlet weak var personHeight: NSLayoutConstraint!
func animateBackgroundHeight() {
print("animate")
UIView.animate(withDuration: 5.0, animations: {
self.personHeight.constant = 19 // personHeight is the IBOutlet to the constraint
self.view.layoutIfNeeded()
})
}
Everything in the view moves when the animation is going on but I'm not sure why. What I want to animate is the height of the view, but not effect the TextField or the button. Think of the GreyView as a background element of the TextField.
Here is a image to show you which constraint I am trying to animate (green one)
Pink box represents the parent view.
Grey Box is the view that I am trying to animate the height of.
The TextField and button are above the Grey Box object.
You may animate the wrong view after all
so to be clear about your question
if you have x view that nested in another view
and want to animate only x view
then you should
make
self.x.layoutIfNeeded()
instead of
self.view.layoutIfNeeded()
I have a UIView that I would like to animate off the screen and then bring another view into the screen. I'm using auto layout and I have a IBOutlet set up for the left and right NSLayoutConstraint. Its seem that with out a width set for the UIView that the view actually doesn't animate off the screen.
I'm trying to use this code to pus the UIView off:
self.gameViewConstraintLeft.constant = -400
UIView.animateWithDuration(1, animations: { () in
self.view.layoutIfNeeded()
})
self.gameViewConstriantRight.constant = -400
UIView.animateWithDuration(1, animations: { () in
self.view.layoutIfNeeded()
})
I have constraints set up on the top(0), left(0) and right(0). Maybe this is my issue that I'm pinning the view to the edge of the screen, but I want this to work on multiple screen sizes, so I'm just not sure the best method of achieving my goal.
You are going to have problems moving the views if they're pinned on multiple sides horizontally or vertically.
I would suggest the following:
Set up width, height, centerX, and centerY constraints for your two views. The width and height can be set to "Equal Widths" and "Equal Heights" with the superview to allow it to work with multiple screen sizes.
Add an IBOutlets to the centerX constraint. Animate by modifying the constant property of this constraint:
self.gameView1ConstraintCenterX.constant = -400
UIView.animateWithDuration(1) {
self.view.layoutIfNeeded()
}
self.gameView2ConstriantCenterX.constant = 0
UIView.animateWithDuration(1) {
self.view.layoutIfNeeded()
}