How to know which rectangle is user viewing? - swiftui

The following scroll view works well. It has buttons to go to next and previous Rectangles. However, the user can scroll without the buttons as well (expected functionality). Then the currentIndex wont change, how to make the currentIndex change according to the rectangle that the user is viewing?
var body: some View {
var currentIndex: Int = 0
ScrollView {
ScrollViewReader { value in
Button("Go to next") {
withAnimation {
value.scrollTo((currentIndex), anchor: .top)
currentIndex += 1
print (currentIndex)
}
}
.padding()
Button("Go to previous") {
withAnimation {
value.scrollTo((currentIndex), anchor: .top)
currentIndex -= 1
print (currentIndex)
}
}
.padding()
ForEach(0..<100) { i in
ZStack {
Rectangle()
.fill(Color.red)
.frame(width: 200, height: 200)
Text (i)
.id(i)
.onAppear{ currentIndex = i
print (currentIndex) }
}
}
}
}}
Edits:
If I add .onAppear{ currentIndex = i, print (currentIndex) but neither it changes to the current object id and it does not get printed as well. What's the reason?

Related

Recrete animated "tab" buttons from iOS Photos

I want to recreate the animated buttons found in the Photos app and shown below. My goal is to use this type of buttons in a TabView(or something similar) instead of the default ones. Does these type of buttons exist in swiftUI? Or what is a good way to create these buttons?
I have written some stupid code to illustrate the problem, but it feels like the wrong approach.
struct ContentView: View {
#State private var selection = 0
var body: some View {
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 10)
.frame(width: 100, height: 50)
.offset(x: CGFloat(selection * 100), y: 0)
HStack {
Button("Tap Me") {
withAnimation {
selection = 0
}
}
Spacer()
Button("Tap Me") {
withAnimation {
selection = 1
}
}
Spacer()
Button("Tap Me") {
withAnimation {
selection = 2
}
}
}
.frame(width: 300)
}
.padding()
.background(.green)
}
}
Output:

SwiftUI extend or recalculate VStack height when children views extend

I have a scrollview that holds "cards" with weather details of locations and the cards extend to show more information when tapped.
I have to use a LegacyScrollView so that the bottomsheet that encompasses the scrollview gets dragged down when the the scroll is at the top.
I can't figure out how to extend the VStack when one of the cards extend. Is there a way to make a GeometryReader or VStack recalculate the needed height to hold all of the views?
I start off with the minHeight set to the window size so when the 1st card is extended, it all shows, but 2 or more extend past the LazyVStack window and get cut off.
If the minHeight on the LaxyVStack isn't set to 0, the cells expand from the middle and their tops get cut off. When it's set to 0, the cells expand downward as desired.
GeometryReader { proxy in
LegacyScrollView(.vertical, showsIndicators: false) {
Spacer(minLength: 40)
LazyVStack(spacing: 0) {
ForEach(mapsViewModel.placedMarkers, id: \.id) { _placeObject1 in
InfoCell(placeObject: _placeObject1)
.environmentObject(mapsViewModel)
.frame(alignment: .top)
Divider().foregroundColor(.white)
}
}
.cornerRadius(25)
.frame(minHeight: 0, alignment: .top)
}
.onGestureShouldBegin{ pan, scrollView in
scrollView.contentOffset.y > 0 || pan.translation(in: scrollView).y < 0
}
.onScroll{ test in
print(test.contentOffset.y)
}
.padding(.top, 30) //padding for top of list
}
and the cell's code is :
struct InfoCell: View {
#StateObject var placeObject: PlaceInfo
#State var expandCell = false
var body: some View {
VStack(alignment: .center, spacing: 0) {
//blahblah
if expandCell {
CellExpanded(placeObject: placeObject, isFirstInList: true)
}
}
.frame(maxWidth: .infinity)
.onTapGesture {
withAnimation { expandCell.toggle() }
}
}
}
and the expand code:
struct InfoCellExpanded: View {
#State var forecast = "Daily"
var placeObject: PlaceInfo
var isFirstInList: Bool
var body: some View {
VStack {
//blah
}
.padding(.horizontal, 10)
.padding(.bottom, 20)
}
}

SwiftUI Crash when calling scrollTo method when view is disappearing

I try to make my ScrollView fixed in a specific place, but the scrollTo method will cause the application to crash.
How to make the ScrollView stay in a fixed place?
I want to control the switching of views by MagnificationGesture to switch from one view to another.
But when Scroll() disappdars, the app crashs.
struct ContentView: View {
#State var tabCount:Int = 1
#State var current:CGFloat = 1
#State var final:CGFloat = 1
var body: some View {
let magni = MagnificationGesture()
.onChanged(){ value in
current = value
}
.onEnded { value in
if current > 2 {
self.tabCount += 1
}
final = current + final
current = 0
}
VStack {
VStack {
Button("ChangeView"){
self.tabCount += 1
}
if tabCount%2 == 0 {
Text("some text")
}else {
Scroll(current: $current)
}
}
Spacer()
HStack {
Color.blue
}
.frame(width: 600, height: 100, alignment: .bottomLeading)
}
.frame(width: 600, height: 400)
.gesture(magni)
}
}
This is ScrollView, I want it can appear and disappear. When MagnificationGesture is changing, scrollview can keep somewhere.
struct Scroll:View {
#Binding var current:CGFloat
let intRandom = Int.random(in: 1..<18)
var body: some View {
ScrollViewReader { proxy in
HStack {
Button("Foreword"){
proxy.scrollTo(9, anchor: .center)
}
}
ScrollView(.horizontal) {
HStack {
ForEach(0..<20) { item in
RoundedRectangle(cornerRadius: 25.0)
.frame(width: 100, height: 40)
.overlay(Text("\(item)").foregroundColor(.white))
.id(item)
}
}
.onChange(of: current, perform: { value in
proxy.scrollTo(13, anchor: .center)
})
}
}
}
}

How we can solve unwanted offset in onEnd gesture in swiftUI 2.0

I have this codes for swipe the rows inside a scrollview, at some first try it works fine, but after some scrolling and swipe it starts giving some value for offset even almost zero, and it makes red background shown in edges! I know, probably would some of you came with padding solution up and it would work and cover unwanted red background but even with this unconvinced solution, the text would seen offseted from other ones! I am thinking this is a bug of SwiftUI, otherwise I am very pleased to know the right answer.thanks
import SwiftUI
struct item: Identifiable
{
var id = UUID()
var name : String
var offset : CGFloat = 0
}
struct ContentView: View {
#State var Items: [item] = []
func findIndex(item: item) -> Int
{
for i in 0...Items.count - 1
{
if item.id == Items[i].id
{
return i
}
}
return 0
}
var body: some View {
ScrollView(.vertical, showsIndicators: false)
{
LazyVStack(alignment: .leading)
{
ForEach(Items) { item in
ZStack
{
Rectangle()
.fill(Color.red)
HStack
{
VStack(alignment: .leading)
{
Text(item.name)
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.background(Color.yellow)
.offset(x: item.offset)
.gesture(
DragGesture()
.onChanged({ (value) in
Items[findIndex(item: item)].offset = value.translation.width
})
.onEnded({ (value) in
Items[findIndex(item: item)].offset = 0
}))
.animation(.easeInOut)
}
}
}
}
}
}
.onAppear()
{
for i in 0...10
{
Items.append(item(name: "item " + String(i)))
}
}
}
}
It is because drag gesture is handled even if you try to scroll vertically and horizontal transition is applied to your offset. So the solution is to add some kind of boundary conditions that detects horizontal-only drag.
Here is just a simple demo (you can think about more accurate one). Tested with Xcode 12 / iOS 14 as kind of enough for demo.
DragGesture()
.onChanged({ (value) in
// make offset only when some limit exceeds or when it definitely sure
// that you drag in horizontal only direction, below is just demo
if (abs(value.translation.height) < abs(value.translation.width)) {
Items[i].offset = value.translation.width
}
})
Update: forgot that I changed also to use indices to avoid afterwards finding by id, so below is more complete snapshot
ForEach(Items.indices, id: \.self) { i in
ZStack
{
Rectangle()
.fill(Color.red)
HStack
{
VStack(alignment: .leading)
{
Text(Items[i].name)
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.background(Color.yellow)
.offset(x: Items[i].offset)
.gesture(
DragGesture()
.onChanged({ (value) in
if (abs(value.translation.height) < abs(value.translation.width)) {
Items[i].offset = value.translation.width
}
})
.onEnded({ (value) in
Items[i].offset = 0.0
}))
.animation(.easeInOut)
}
}
}

Using ScrollViewReader in SwiftUI how can I highlight the row I have scrolled to?

If I create this simple ScrollView containing 100 rows and create a button to scroll to row 60, I would like to highlight that I am at row 60, maybe with a background colour or similar. I cannot figure out how to do this.
var body: some View {
ScrollView {
ScrollViewReader { value in
Button("Jump to #60") {
value.scrollTo(60, anchor: .center)
}
ForEach(0..<100) { i in
Text("Line \(i)")
}
}
}
}
Here is possible approach - you introduce state for highlighted row, which can modify by needs, including for scroll to
Tested with Xcode 12b5 / iOS 14
#State private var highlighted: Int?
var body: some View {
ScrollView {
ScrollViewReader { value in
Button("Jump to #60") {
value.scrollTo(60, anchor: .center)
highlighted = 60
}
ForEach(0..<100) { i in
Text("Line \(i)")
.frame(maxWidth: .infinity)
.background(i == highlighted ? Color.gray : Color.clear)
}
}
}
}
Found a way.
Create a function to set a colour if the current index and the scrollTo target are equal.
func highlight(index: Int, target: Int) -> Color {
if target == index {
return .black
} else {
return .clear
}
Then use it to set background, border etc.
Text("Line \(i)")
.border(highlight(index: i, target: 60), width: 5)