ScrollViewReader anchor center creates free space - swiftui

I want to zoom in on a specific element in the scrollview. I want it to be an anchor center. When zooming to the upper left elements, no problem, it works properly. But when you zoom in on the top right and bottom elements, space is created around it. I showed in the pictures. how can i solve this?
thank you.
GeometryReader { geo in
ScrollView(detect.scrolID != nil ? [.horizontal,.vertical] : []) {
ScrollViewReader { reader in
BoardView(changeFrame: self.changeFrame, spotFrames: $spotFrames)
.frame(width: detect.scrolID != nil ? geo.size.width * 2 : geo.size.width , height: detect.scrolID != nil ? geo.size.width * 2 : geo.size.width)
.onChange(of: detect.scrolID){ value in
if value != nil {
if camerazoom{
reader.scrollTo(value, anchor: .center)
}
}
}
}
}
.background(Color.blue)
}
first loaded version of the page
Works correctly when zooming in on top left elements
When zooming to the following elements, these gaps appear

Related

ScrollView shows smudge at the bottom of the frame when scrolling the items

I am using the GridItems inside the ScrollView to show ten circles horizontally. It works fine but there are lines of smudge shown at the bottom border line of ScrollView container. I tried,
Addind padding to ScrollView.
Adding padding to HStack.
Any recommendations would be appreciated! I captured the screenshot scrolling all the way to the left and beyond holding the press.
Using iPhone 13 mini simulator,
here is the screenshot
struct HomeView: View {
var body: some View {
ScrollView.init([.vertical]) {
Section(header: Text("Today's Items").font(.system(size: 20.0, weight:.semibold, design: .rounded))) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 10) {
ForEach(0..<20) { _ in
Circle()
.fill(Color.random)
.frame(width: 100, height: 100)
}
}.padding(.vertical).background(.blue)
}.padding(.vertical).background(.orange)
}
...
}
}
}
UPDATE: With help from Yrb, I verified that I only see this in iPhone 12 and 13 mini simulators only (with 13 device being worse). But I don't own either model physically. If anybody can test it on the physical devices and share the observation I would appreciate it.
Add padding to HStack, not to ScrollView

SwiftUI contextmenu wrong location

I am trying to add a contextmenu to images I display, but when I longpress, the contextmenu opens in a wrong location (see image)
The code I use is:
ForEach(self.document.instruments) { instrument in
Image(instrument.text)
.resizable()
.frame(width: 140, height: 70)
.position(self.position(for: instrument, in: geometry.size))
.gesture(self.singleTapForSelection(for: instrument))
.gesture(self.dragSelectionInstrument(for: instrument))
.shadow(color: self.isInstrumentSelected(instrument) ? .blue : .clear, radius: 10 * self.zoomScale(for: instrument))
.contextMenu {
Button {
print("Deleted selected")
} label: {
Label("Delete", systemImage: "trash")
}
}
}
Update of issue
The issue I still face (as my last comment), is that when I drag an image, there seems to be a delay, as the image remains on its position for a short moment and then moves to the dragged position. I already found out that when I have the contextMenu in the code, the delay dragging happens, when commented out, it works fine.
This is the code I have:
ForEach(self.document.instruments) { instrument in
Image(instrument.text)
.resizable()
.frame(width: 140, height: 70)
.contextMenu {
Button {
print("Deleted selected")
} label: {
Label("Delete", systemImage: "trash")
}
}
.position(self.position(for: instrument, in: geometry.size))
.shadow(color: self.isInstrumentSelected(instrument) ? .blue : .clear, radius: 10 * self.zoomScale(for: instrument))
.gesture(self.singleTapForSelection(for: instrument))
.gesture(self.dragSelectionInstrument(for: instrument))
}
I even tried moving the gestures above the contextmenu, but didn't work.
Please help
Latest Update
Nm, it was only on the simulator, on my physical iPad it works fine
The problem is that your code applies the contextMenu modifier after the position modifier.
Let's consider this slightly modified example:
ZStack {
GeometryReader { geometry in
ForEach(self.document.instruments, id: \.id) { instrument in
Image(instrument.text)
.frame(width: 140, height: 70)
.position(self.position(for: instrument, in: geometry.size))
.contextMenu { ... }
}
}
}
In the SwiftUI layout system, a parent view is responsible for assigning positions to its child views. A view modifier acts as the parent of the view it modifies. So in the example code:
ZStack is the parent of GeometryReader.
GeometryReader is the parent of ForEach.
ForEach is the parent of contextMenu.
contextMenu is the parent of position.
position is the parent of frame.
frame is the parent of Image.
Image is not a parent. It has no children.
(Sometimes the parent is called the “superview” and the child is called the “subview”.)
When contextMenu needs to know where to draw the menu on the screen, it looks at the position given to it by its parent, the ForEach, which gets it from the GeometryReader, which gets it from the ZStack.
When Image needs to know where to draw its pixels on the screen, it looks at the position given to it by its parent, which is the frame modifier, and the frame modifier gets the position from the position modifier, and the position modifier modifies the position given to it by the contextMenu.
This means that the position modifier does not affect where the contextMenu draws the menu.
Now let's rearrange the code so contextMenu is the child of position:
ZStack {
GeometryReader { geometry in
ForEach(self.document.instruments, id: \.id) { instrument in
Image(instrument.text)
.frame(width: 140, height: 70)
.contextMenu { ... }
.position(self.position(for: instrument, in: geometry.size))
}
}
}
Now the contextMenu gets its position from the position modifier, which modifies the position given to it by the ForEach. So in this scenario, the position modifier does affect the where the contextMenu draws the menu.

SwiftUI - view expand from the bottom of frame

I'd like to display a number of values as a continuous value from 0 to 1. I'd like them to grow from the bottom up, from 0 displaying no value, to 1 displaying a full height.
However, I'm unable to make it "grow from the bottom". I'm not sure what a better term for this is - it's a pretty simple vertical gauge, like a gas gauge in a car. I'm able to make it grow from the middle, but can't seem to find a way to make it grow from the bottom. I've played with mask and clipShape and overlay - but it must be possible to do this with just a simple View, and calculations on its height. I'd specifically like to able to show overlapping gauges, as the view below demonstrates.
My ContentView.swift is as follows:
import SwiftUI
struct ContentView: View {
// binding values with some defaults to show blue over red
#State var redPct: CGFloat = 0.75
#State var bluePct: CGFloat = 0.25
let DISP_PCT = 0.8 // quick hack - the top "gauge" takes this much so the sliders display below
var body: some View {
GeometryReader { geom in
VStack {
ZStack {
// neutral background
Rectangle()
.fill(Color.gray)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT)
// the first gauge value display
Rectangle()
.fill(Color.red)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT * redPct)
// the second gauge value, on top of the first
Rectangle()
.fill(Color.blue)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT * bluePct)
}
HStack {
Slider(value: self.$redPct, in: 0...1)
Text("Red: \(self.redPct, specifier: "%.2f")")
}
HStack {
Slider(value: self.$bluePct, in: 0...1)
Text("Red: \(self.bluePct, specifier: "%.2f")")
}
}
}
}
}
As you play with the sliders, the red/blue views grows "out" from the middle. I would like them to grow "up" from the bottom of its containing view.
I feel like this is poorly worded - if any clarification is needed, please don't hesitate to ask!
Any help would be greatly appreciated!
You can't have them all in the same stacks. The easiest way to do this is to have your gray rectangle be your case view, and then overlay the others on top in VStacks with Spacers like this:
// neutral background
Rectangle()
.fill(Color.gray)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT)
.overlay (
ZStack {
VStack {
Spacer()
// the first gauge value display
Rectangle()
.fill(Color.red)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT * redPct)
}
VStack {
Spacer()
// the second gauge value, on top of the first
Rectangle()
.fill(Color.blue)
.frame(width: geom.size.width, height: geom.size.height * DISP_PCT * bluePct)
}
}
)
The overlay contains them, and the spacers push your rectangles down to the bottom of the stacks.

How do I know a scrollview element is about to leave/entering the screen, so I can create an animation

I have a vertical scrollView with a lot of items. As the user scrolls I want to do some nice effect, for example, like a carousel or something. In fact, less dramatic, just the cells being shifted to the right as they are cut as the scroll out of view.
This is what I have to test.
ScrollView {
ForEach(0...50) { item in
GeometryReader { geometry in
Text(item)
.onChange(of: geometry.frame(in: .named("xxx")), perform: { value in
if item == 1 {
print(value.minY)}
})
}
}
}
.coordinateSpace(name: "xxx")
I have added this coordinateSpace and this .onChange, to see what is happening with one element in particular, element number 1.
I see number one's value.minY goes from 0 to -400, if I scroll up. When Y is 0, is when this element is fully on view.
But if I have N elements on that view, it will be very complex to know when every element is entering the view. At least I am not seeing how.
The idea is to apply an effect like a shift, rotation or whatever, when the element starts to go off view and unwind the effect when the element is full on view.
How?
I apologize if I misread your question, but your just trying to animate the objects when the come on or off the screen based on their location within the coordinate space? If so, here's an example of how to do it. It's basically what you had except you can use the geometry immediately on modifiers and don't need the .onChange.
The below code has a top & bottom threshold, calculates each item's % into the threshold and then updates modifiers accordingly.
import SwiftUI
struct ScrollAnimationView: View {
var body: some View {
ScrollView {
ForEach(0..<50) { item in
GeometryReader { geometry in
Text("NUMBER \(item), PERCENT: \(getPercentOfAnimation(geo: geometry))")
.font(.headline)
.offset(x: getPercentOfAnimation(geo: geometry) * UIScreen.main.bounds.width)
.opacity(1.0 - Double(getPercentOfAnimation(geo: geometry)))
.rotationEffect(
Angle(degrees: 10 * Double(getPercentOfAnimation(geo: geometry)))
)
.scaleEffect(1.0 - getPercentOfAnimation(geo: geometry))
}
}
}
.coordinateSpace(name: "xxx")
}
func getPercentOfAnimation(geo: GeometryProxy) -> CGFloat {
let threshold: CGFloat = UIScreen.main.bounds.height * 0.4
let currentY: CGFloat = geo.frame(in: .named("xxx")).minY
if currentY < threshold {
return 1 - currentY / threshold
}
let topThreshold: CGFloat = UIScreen.main.bounds.height * 0.6
if currentY > topThreshold {
return (currentY - topThreshold) / threshold
}
return 0
}
}

SwiftUI ScrollView and alignment

Okay, I know SwiftUI is a shift in thinking, especially coming from a world of HTML and css. But I've spent like 4 days trying to get something to work that I feel should be pretty easy and just can't so please help!
I have an app where one screen is a table of results, dynamic data that could be one or two rows/columns but could also be hundreds. So I want to be able to scroll around in cases where the table is huge.
I've replicated my setup and reproduced my problems in a Swift playground like so
import Foundation
import UIKit
import PlaygroundSupport
import SwiftUI
struct ContentView : View {
var cellSize: CGFloat = 50
var numRows: Int = 3
var numCols: Int = 3
var body : some View {
ZStack {
ScrollView([.horizontal,.vertical]) {
HStack( spacing: 0) {
VStack ( spacing: 0) {
ForEach(0 ..< numRows, id: \.self) { row in
Text("row " + row.description)
.frame( height: self.cellSize )
}
}
ForEach(0 ..< self.numCols, id: \.self) { col in
VStack( spacing: 0) {
ForEach(0 ..< self.numRows, id: \.self) { row in
Rectangle()
.stroke(Color.blue)
.frame( width: self.cellSize, height: self.cellSize )
}
}
}
}
.frame( alignment: .topLeading)
}
}
}
}
let viewController = UIHostingController(rootView: ContentView())
PlaygroundPage.current.liveView = viewController
Here's what I get when the grid is 3x3
I know things like to center by default in SwiftUI, but why isn't that .frame( alignment: .topLeading) on the HStack causing the table to be aligned to the upper left corner of the screen?
Then even worse, once that table gets large, here's what I get:
Still not aligned to the top left, which would make sense as a starting point.
When I scroll left, I can't even get to the point where I can see my header column
The view bounces me away from the edges when I get close. Like I can get to the point where I can see the top edge of the table, but it bounces me back right away.
A ton of whitespace to the right, which probably correlates to me not seeing my header columns on the left.
What am I doing wrong here? I'm exhausted trying all different frame and alignment options on various Views in here.