Animate ViewBuilder content size change in SwiftUI - ios

I am wondering how I can animate the content size of a ViewBuilder view. I have this:
struct CardView<Content>: View where Content: View {
private let content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
VStack(spacing: 0) {
content
.padding(16)
}
.background(.white)
.cornerRadius(14)
.shadow(color: .black.opacity(0.07), radius: 12, x: 0, y: 2)
}
}
I would like to animate any size changes to content, but I can't find a nice way of doing this. I found two ways that work:
Using animation(.linear) in CardView works, but is deprecated and discouraged since I have no value to attach the animation to.
Using withAnimation inside content when changing the content works, too, but I would like to encapsulate this behaviour in CardView. CardView is heavily reused and doing it in content is easy to forget and also not where this behaviour belongs in my opinion.
I also tried using GeometryReader but could not find a good way of doing it.

Here is an approach for you:
You may take look at this link as well:
How to replace deprecated .animation() in SwiftUI?
struct ContentView: View {
#State private var cardSize: CGSize = CGSize(width: 150, height: 200)
var body: some View {
VStack {
CardView(content: {
Color.red
.overlay(Image(systemName: "dollarsign.circle").resizable().scaledToFit().padding())
.onTapGesture {
cardSize = CGSize(width: cardSize.width + 50, height: cardSize.height + 50)
}
}, cardSize: cardSize)
}
}
}
struct CardView<Content>: View where Content: View {
let content: Content
let cardSize: CGSize
init(#ViewBuilder content: () -> Content, cardSize: CGSize) {
self.content = content()
self.cardSize = cardSize
}
var body: some View {
content
.frame(width: cardSize.width, height: cardSize.height)
.cornerRadius(14)
.padding(16)
.background(.white)
.shadow(color: .black.opacity(0.07), radius: 12, x: 0, y: 2)
.animation(.easeInOut, value: cardSize)
}
}

You might find this useful.
It uses a looping animation and a user gesture for add size and resting.
struct PilotTestPage: View {
#State private var cardSize = CGSize(width: 150, height: 200)
var body: some View {
return ZStack {
Color.yellow
CardView() {
Color.clear
.overlay(
Image(systemName: "dollarsign.circle")
.resizable()
.scaledToFit()
.padding()
)
}
.frame(
width: cardSize.width
,height: cardSize.height
)
.onTapGesture {
withAnimation {
cardSize = CGSize(
width: cardSize.width + 50
,height: cardSize.height + 50
)
}
}
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(.red)
.frame(
width: 200
,height: 44
)
.offset(y: -300)
.onTapGesture {
withAnimation {
cardSize = CGSize(
width: 150
,height: 200
)
}
}
}
.ignoresSafeArea()
}
struct CardView<Content>: View where Content: View {
let content: Content
init(
#ViewBuilder content: () -> Content
) {
self.content = content()
}
#State private var isAtStart = true
var body: some View {
ZStack {
content
.background(
RoundedRectangle(cornerRadius: 12)
.fill(.white)
.shadow(
color: .black.opacity(0.25)
,radius: 12
,x: 0
,y: 2
)
)
}
.scaleEffect(isAtStart ? 0.9 : 1.0)
.rotationEffect(.degrees(isAtStart ? -2 : 2))
.onAppear {
withAnimation(
.easeInOut(duration: 1)
.repeatForever()
) {
self.isAtStart.toggle()
}
}
}
}
}

Related

How do I add a vertical swipe up gesture(to go to a different view) in a scrollview in SwiftUI

I designed a SwiftUI view which is a scrollview. Now I need to add a vertical swipe gesture to it which shall take it to a different view. I tried to do it using the tabView and adding a rotating effect of -90 degrees to it. But that rotates my original view too and that's not what I want. I couldn't find any relevant help in SwiftUI which deals with swiping up a scrollview to a new view.
Here's my code..
the vertical swipe I achieved using this. But my view get rotated. Setting other angles disappears the view somehow. I am new to SwiftUI, I am stuck on it for a week now.1
GeometryReader { proxy in
TabView {
ScrollView {
VStack(alignment: .center) {
ZStack(alignment: .leading) {
Image("Asset 13").resizable().frame(width: percentWidth(percentage: 100), height: percentHeight(percentage: 50), alignment: .top)
HStack {
Spacer()
Image("Asset 1")//.padding(.bottom, 130)
Spacer()
}.padding(.bottom, 150)
HStack {
VStack(spacing:2) {
Text("followers").foregroundColor(.white).padding(.leading, 20)
HStack {
Image("Asset 3")
Text("10.5k").foregroundColor(.white)
}
}
Spacer()
VStack {
Image("Asset 10").padding(.trailing)
Text("300K Review ").foregroundColor(.white)
}
}.background(Image("Asset 2").resizable().frame(width: percentWidth(percentage: 100), height: percentHeight(percentage: 6), alignment: .leading))
.padding(.top, 410)
HStack {
Spacer()
Image("Asset 14").resizable().frame(width: percentWidth(percentage: 50), height: percentHeight(percentage: 25), alignment: .center)
Spacer()
}.padding(.top, 390)
}
VStack(spacing: 4) {
Text("Karuna Ahuja | Yoga Instructor").font(Font.custom(FontName.bold, size: 22))
Text("12 Years of Experience with Bhartiya Yog Sansthan").tracking(-1).font(Font.custom(FontName.light, size: 16)).opacity(0.4)
}
Divider()
HStack {
ZStack {
Image("Asset 6").resizable().frame(width: percentWidth(percentage: 30), height: percentHeight(percentage: 12), alignment: .center)
VStack {
Image("Asset 5").resizable().frame(width: percentWidth(percentage: 8), height: percentHeight(percentage: 4), alignment: .center)
Text("245").font(Font.custom(FontName.bold, size: 16))
Text("Video").font(Font.custom(FontName.medium, size: 16)).opacity(0.5)
}
}
ZStack {
Image("Asset 6").resizable().frame(width: percentWidth(percentage: 30), height: percentHeight(percentage: 12), alignment: .center)
VStack {
Image("Asset 7").resizable().frame(width: percentWidth(percentage: 8), height: percentHeight(percentage: 4), alignment: .center)
Text("45").font(Font.custom(FontName.bold, size: 16))
Text("Live Class").font(Font.custom(FontName.medium, size: 16)).opacity(0.5)
}
}
ZStack {
Image("Asset 6").resizable().frame(width: percentWidth(percentage: 30), height: percentHeight(percentage: 12), alignment: .center)
VStack {
Image("Asset 9").resizable().frame(width: percentWidth(percentage: 8), height: percentHeight(percentage: 4), alignment: .center)
Text("245").font(Font.custom(FontName.bold, size: 16))
Text("Sessions").font(Font.custom(FontName.medium, size: 16)).opacity(0.5)
}
}
}
Divider()
Text("Shine bright so that your light leads other. I'm a fitness junkie, high-energy yoga instructor. Let's make fitness FUN!").font(Font.custom(FontName.normal, size: 16)).tracking(-1).opacity(0.7).padding([.leading,.trailing], 6)
VideoPlayer(player: AVPlayer(url: videoUrl))
.frame(height: 320)
Spacer()
}.gesture(DragGesture(minimumDistance: 20, coordinateSpace: .global)
.onEnded { value in
let horizontalAmount = value.translation.width as CGFloat
let verticalAmount = value.translation.height as CGFloat
if abs(horizontalAmount) > abs(verticalAmount) {
print(horizontalAmount < 0 ? "left swipe" : "right swipe")
} else {
print(verticalAmount < 0 ? "up swipe" : "down swipe")
}
})
}.edgesIgnoringSafeArea(.all)
.ignoresSafeArea()
Text("this")
Text("this")
Text("this")
// ForEach(colors, id: \.self) { color in
// color // Your cell content
// }
// .rotationEffect(.degrees(-90)) // Rotate content
// .frame(
// width: proxy.size.width,
// height: proxy.size.height
// )
}
.frame(
width: proxy.size.height, // Height & width swap
height: proxy.size.width
)
.rotationEffect(.degrees(90), anchor: .topLeading) // Rotate TabView
.offset(x: proxy.size.width) // Offset back into screens bounds
.tabViewStyle(
PageTabViewStyle(indexDisplayMode: .never)
)
}
The only pure SwiftUI way I see is to do your own ScrollView implementation, which is not too complicated. This example has two views on top of each other. If you drag the first view further up than to the middle of the screen, it swipes away to reveal the second view.
struct ContentView: View {
#State private var offset = CGFloat.zero
#State private var dragOffset = CGFloat.zero
#State private var viewHeight = CGFloat.zero
var body: some View {
GeometryReader { fullgeo in
ZStack(alignment: .top) {
SecondView()
// necessary for second view to resize individually
.frame(height: fullgeo.size.height)
ScrollingView()
.overlay( GeometryReader { geo in Color.clear.onAppear { viewHeight = geo.size.height }})
.offset(y: offset + dragOffset)
.gesture(DragGesture()
.onChanged { value in
dragOffset = value.translation.height
}
.onEnded { value in
withAnimation(.easeOut) {
dragOffset = .zero
offset += value.predictedEndTranslation.height
// if bottom dragged higher than 50% of screen > second view
if offset < -(viewHeight - fullgeo.size.height/2) {
dragOffset = -viewHeight
return
}
// else constrain to top / bottom of ScrollingView
offset = max(min(offset, 0), -(viewHeight - fullgeo.size.height))
}
}
)
}
}
}
}
struct ScrollingView: View {
var body: some View {
VStack {
Text("View Top").font(.headline)
ForEach(0..<10) { _ in
Text("Content")
.frame(width: 200, height: 100)
.background(.white)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.gray)
}
}
struct SecondView: View {
var body: some View {
Text("View Bottom").font(.headline)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.orange)
}
}
Use this code:
import SwiftUI
struct PullToRefreshView: View
{
private static let minRefreshTimeInterval = TimeInterval(0.2)
private static let triggerHeight = CGFloat(100)
private static let indicatorHeight = CGFloat(100)
private static let fullHeight = triggerHeight + indicatorHeight
let backgroundColor: Color
let foregroundColor: Color
let isEnabled: Bool
let onRefresh: () -> Void
#State private var isRefreshIndicatorVisible = false
#State private var refreshStartTime: Date? = nil
init(bg: Color = .neutral0, fg: Color = .neutral90, isEnabled: Bool = true, onRefresh: #escaping () -> Void)
{
self.backgroundColor = bg
self.foregroundColor = fg
self.isEnabled = isEnabled
self.onRefresh = onRefresh
}
var body: some View
{
VStack(spacing: 0)
{
LazyVStack(spacing: 0)
{
Color.clear
.frame(height: Self.triggerHeight)
.onAppear
{
if isEnabled
{
withAnimation
{
isRefreshIndicatorVisible = true
}
refreshStartTime = Date()
}
}
.onDisappear
{
if isEnabled, isRefreshIndicatorVisible, let diff = refreshStartTime?.distance(to: Date()), diff > Self.minRefreshTimeInterval
{
onRefresh()
}
withAnimation
{
isRefreshIndicatorVisible = false
}
refreshStartTime = nil
}
}
.frame(height: Self.triggerHeight)
indicator
.frame(height: Self.indicatorHeight)
}
.background(backgroundColor)
.ignoresSafeArea(edges: .all)
.frame(height: Self.fullHeight)
.padding(.top, -Self.fullHeight)
}
private var indicator: some View
{
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: foregroundColor))
.opacity(isRefreshIndicatorVisible ? 1 : 0)
}
}
sample:
ScrollView{
VStack(spacing:0) {
//top of scrollView
PullToRefreshView{
//todo
}
}
}

How to draw circles on random position in view borders

Help me please to catch how to draw circles on random position in view borders.
Red View options
.background(Color.red)
.padding(40)
Circles:
var body: some View
{
Circle()
.strokeBorder(Color.blue, lineWidth: 2.0, antialiased: true)
.frame(width: 50, height: 50, alignment: .center)
.overlay(Text(title))
}
I try to add circles on red view in random position using .position option, but you can see its not working:
The main problem is how to know width and height of red view on any device and set .position of circles.
You can use GeometryReader for reading the size of View, like in the example:
struct ContentView: View {
var body: some View {
GeometryReader { proxy in
ForEach(0...50, id:\.self) { index in
CirclesView(index: index)
.offset(x: CGFloat.random(in: 0.0...proxy.size.width), y: CGFloat.random(in: 0.0...proxy.size.height))
}
}
.background(Color.red)
.ignoresSafeArea()
}
}
struct CirclesView: View {
let index: Int
var body: some View {
Circle()
.strokeBorder(Color.blue, lineWidth: 2.0, antialiased: true)
.frame(width: 50, height: 50, alignment: .center)
.overlay(Text(String(describing: index)))
}
}
Update:
struct ContentView: View {
var body: some View {
GeometryReader { proxy in
ForEach(0...50, id:\.self) { index in
CirclesView(index: index, offset: logicalFunction(size: proxy.size))
}
}
.background(Color.red)
.ignoresSafeArea()
}
func logicalFunction(size: CGSize) -> CGSize {
// Do your works here!
let width: CGFloat = CGFloat.random(in: 0.0...size.width)
let height: CGFloat = CGFloat.random(in: 0.0...size.height)
return CGSize(width: width, height: height)
}
}
struct CirclesView: View {
let index: Int
let offset: CGSize
var body: some View {
Circle()
.strokeBorder(Color.blue, lineWidth: 2.0, antialiased: true)
.frame(width: 50, height: 50, alignment: .center)
.overlay(Text(String(describing: index)))
.offset(offset)
}
}

SwiftUI: Why isn't my view being shown off-screen as its frame suggests it should?

I have an interface where I want to be able to toggle a Side Menu and also a toolbar. The Side Menu slides in/out from the right and the Toolbar from the bottom.
I'm making it generic so it's reusable to whatever content one wants to use.
The code is this:
struct ControlsOverlayView<ToolbarContent: View, MenuContent: View>: View {
let menuWidth: CGFloat
let isMenuActive: Bool
let onMenuHide: () -> Void
let menuContent: MenuContent
let toolbarHeight: CGFloat
let isToolbarActive: Bool
let onToolbarHide: () -> Void
let toolbarContent: ToolbarContent
init(menuWidth: CGFloat = 270,
isMenuActive: Bool = true,
onMenuHide: #escaping () -> Void,
toolbarHeight: CGFloat = 44,
isToolbarActive: Bool = true,
onToolbarHide: #escaping () -> Void,
#ViewBuilder menuContent: () -> MenuContent,
#ViewBuilder toolbarContent: () -> ToolbarContent) {
self.menuWidth = menuWidth
self.isMenuActive = isMenuActive
self.onMenuHide = onMenuHide
self.menuContent = menuContent()
self.toolbarHeight = toolbarHeight
self.isToolbarActive = isToolbarActive
self.onToolbarHide = onToolbarHide
self.toolbarContent = toolbarContent()
}
var body: some View {
ZStack {
GeometryReader { _ in
EmptyView()
}
.background(Color.gray.opacity(0.3))
.opacity(self.isMenuActive ? 1.0 : 0.0)
.animation(Animation.easeIn.delay(0.25))
.onTapGesture {
self.onMenuHide()
}
GeometryReader { geometry in
VStack {
HStack {
Spacer()
let space: CGFloat = 0.0
let offset = self.isMenuActive ? space : space + self.menuWidth
let toolbarHeight = isToolbarActive ? self.toolbarHeight : 0
self.menuContent
.frame(width: self.menuWidth, height: geometry.size.height - toolbarHeight, alignment: .center)
.background(Color.red)
.offset(x: offset)
.animation(.default)
}
let offset = self.isToolbarActive ? 0 : -self.toolbarHeight
self.toolbarContent
.frame(width: geometry.size.width,
height: self.toolbarHeight,
alignment: .center)
.background(Color.yellow)
.offset(y: offset)
.animation(.default)
}
}
}
}
}
struct ControlsOverlayView_Previews: PreviewProvider {
static var previews: some View {
ControlsOverlayView(menuWidth: 270,
isMenuActive: true,
onMenuHide: {},
toolbarHeight: 44,
isToolbarActive: false,
onToolbarHide: {}) {
Text("Menu Content")
} toolbarContent: {
Text("Toolbar Content")
}
}
}
The Problem: Given the preview settings, I don't think I should see the toolbar. And yet I do. Attached is a screenshot of the Canvas with the toolbarContent.frame(...) line highlighted in code. You can see that it shows a frame to be drawn offscreen, but the content is not drawn there with it.
I was following from the code to make the side menu slide in/out on the horizontal axis and thought I just need to do essentially the same for the toolbar, but as you can see, that approach doesn't work.
So I made it work, but I still have no idea why.
Using this new body code, I set the frame of the HStack, and adjusted the offset. Strange is that given the offset value of 'self.toolbarHeight / 2' I don't think it should yield the desired result, and yet it does:
var body: some View {
ZStack {
GeometryReader { _ in
EmptyView()
}
.background(Color.gray.opacity(0.3))
.opacity(self.isMenuActive ? 1.0 : 0.0)
.animation(Animation.easeIn.delay(0.25))
.onTapGesture {
self.onMenuHide()
}
GeometryReader { geometry in
VStack {
let toolbarHeight = isToolbarActive ? self.toolbarHeight : 0
HStack {
Spacer()
let space: CGFloat = 0.0 //geometry.size.width - self.menuWidth
let offset = self.isMenuActive ? space : space + self.menuWidth
self.menuContent
.frame(width: self.menuWidth, height: geometry.size.height - toolbarHeight, alignment: .center)
.background(Color.red)
.offset(x: offset)
.animation(.default)
}
.frame(width: geometry.size.width,
height: geometry.size.height - toolbarHeight,
alignment: .center)
// Why does this work? You'd think it should be self.toolbarHeight without the factor of 2
let offset = self.isToolbarActive ? 0 : self.toolbarHeight/2
self.toolbarContent
.frame(width: geometry.size.width,
height: self.toolbarHeight,
alignment: .center)
.background(Color.yellow)
.offset(y: offset)
.animation(.default)
}
}
}
}

SwiftUI - stacking views like the notifications on the Lockscreen

I am trying to stack views of my app like the notifications on the lockscreen. (See attachment) I want them to be able to stack by clicking the button and expand on a click on the view. I have tried many things on VStack and setting the offsets of the other views but without success.
Is there maybe a standardized and easy way of doing this?
Somebody else have tried it and has a tip for me programming this?
Thanks
Notifications stacked
Notifications expanded
EDIT:
So this is some Code that I've tried:
VStack{
ToDoItemView(toDoItem: ToDoItemModel.toDo1)
.background(Color.green)
.cornerRadius(20.0)
.padding(.horizontal, 8)
.padding(.bottom, -1)
.zIndex(2.0)
ToDoItemView(toDoItem: ToDoItemModel.toDo2)
.background(Color.blue)
.cornerRadius(20.0)
.padding(.horizontal, 8)
.padding(.bottom, -1)
.zIndex(1.0)
.offset(x: 0.0, y: offset)
ToDoItemView(toDoItem: ToDoItemModel.toDo3)
.background(Color.yellow)
.cornerRadius(20.0)
.padding(.horizontal, 8)
.padding(.bottom, -1)
.offset(x: 0.0, y: offset1)
ToDoItemView(toDoItem: ToDoItemModel.toDo3)
.background(Color.gray)
.cornerRadius(20.0)
.padding(.horizontal, 8)
.padding(.bottom, -1)
.offset(x: 0.0, y: offset1)
ToDoItemView(toDoItem: ToDoItemModel.toDo3)
.background(Color.pink)
.cornerRadius(20.0)
.padding(.horizontal, 8)
.padding(.bottom, -1)
.offset(x: 0.0, y: offset1)
}
Button(action: {
if(!stacked){
stacked.toggle()
withAnimation(.easeOut(duration: 0.5)) { self.offset = -100.0 }
withAnimation(.easeOut(duration: 0.5)) { self.offset1 = -202.0 }
}
else {
stacked.toggle()
withAnimation(.easeOut(duration: 0.5)) { self.offset = 0 }
withAnimation(.easeOut(duration: 0.5)) { self.offset1 = 0 }
}
}, label: {
Text("Button")
})
So the first problem is that view 4 and 5 are not stacked on z behind view 3.
The second problem is that the button is not getting under the 5th view. (the frame/VStack of the views is still the old size)
Views stacked vertical
Views stacked on z
Another way using ZStack if your interested.
Note-:
I have declared variables globally in view, you can create your own model if you wish.
struct ContentViewsss: View {
#State var isExpanded = false
var color :[Color] = [Color.green,Color.gray,Color.blue,Color.red]
var opacitys :[Double] = [1.0,0.7,0.5,0.3]
var height:CGFloat
var width:CGFloat
var offsetUp:CGFloat
var offsetDown:CGFloat
init(height:CGFloat = 100.0,width:CGFloat = 400.0,offsetUp:CGFloat = 10.0,offsetDown:CGFloat = 10.0) {
self.height = height
self.width = width
self.offsetUp = offsetUp
self.offsetDown = offsetDown
}
var body: some View {
ZStack{
ForEach((0..<color.count).reversed(), id: \.self){ index in
Text("\(index)")
.frame(width: width , height:height)
.padding(.horizontal,isExpanded ? 0.0 : CGFloat(-index * 4))
.background(color[index])
.cornerRadius(height/2)
.opacity(isExpanded ? 1.0 : opacitys[index])
.offset(x: 0.0, y: isExpanded ? (height + (CGFloat(index) * (height + offsetDown))) : (offsetUp * CGFloat(index)))
}
Button(action:{
withAnimation {
if isExpanded{
isExpanded = false
}else{
isExpanded = true
}
}
} , label: {
Text(isExpanded ? "Stack":"Expand")
}).offset(x: 0.0, y: isExpanded ? (height + (CGFloat(color.count) * (height + offsetDown))) : offsetDown * CGFloat(color.count * 3))
}.padding()
Spacer().frame(height: 550)
}
}
I find it a little easier to get exact positioning using GeometryReader than using ZStack and trying to fiddle with offsets/positions.
This code has some numbers hard-coded in that you might want to make more dynamic, but it does function. It uses a lot of ideas you were already doing (zIndex, offset, etc):
struct ToDoItemModel {
var id = UUID()
var color : Color
}
struct ToDoItemView : View {
var body: some View {
RoundedRectangle(cornerRadius: 20).fill(Color.clear)
.frame(height: 100)
}
}
struct ContentView : View {
#State private var stacked = true
var todos : [ToDoItemModel] = [.init(color: .green),.init(color: .pink),.init(color: .orange),.init(color: .blue)]
func offsetForIndex(_ index : Int) -> CGFloat {
CGFloat((todos.count - index - 1) * (stacked ? 4 : 104) )
}
func horizontalPaddingForIndex(_ index : Int) -> CGFloat {
stacked ? CGFloat(todos.count - index) * 4 : 4
}
var body: some View {
VStack {
GeometryReader { reader in
ForEach(Array(todos.reversed().enumerated()), id: \.1.id) { (index, item) in
ToDoItemView()
.background(item.color)
.cornerRadius(20)
.padding(.horizontal, horizontalPaddingForIndex(index))
.offset(x: 0, y: offsetForIndex(index))
.zIndex(Double(index))
}
}
.frame(height:
stacked ? CGFloat(100 + todos.count * 4) : CGFloat(todos.count * 104)
)
.border(Color.red)
Button(action: {
withAnimation {
stacked.toggle()
}
}) {
Text("Unstack")
}
Spacer()
}
}
}

Xcode 11 Beta 5 - Modal triggers only once

I just upgrade to Xcode 11 Beta 5 and update my SwiftUI project.
In previous version I wanted to use PresentationLink component to show up a modal. I had the same problem than now, the modal has only shown once. I thought it was a bug as I saw in other SO posts. So I tried my chance by upgrading to Beta 5 but still no luck.
I noticed that this behaviour seems to be caused by wrapping in a ScrollView component. If I delete the ScrollView component everything works fine as expected.
Here's the code:
struct HomeList : View {
var listViewItems = listViewItemsData
#State var show = false
var body: some View {
VStack {
HStack {
VStack(alignment: .leading) {
Text("Project title").font(.largeTitle).fontWeight(.heavy)
Text("Project subtitle").foregroundColor(Color.gray)
}
Spacer()
}.padding(.top, 78).padding(.leading, 60)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 30) {
ForEach(listViewItems) { item in
GeometryReader { geometry in
Button(action: { self.show.toggle()}) {
ListView(title: item.title, image: item.image, color: item.color, destination: item.destination)
.rotation3DEffect(Angle(degrees: Double((geometry.frame(in: .global).minX - 30) / -30)), axis: (x: 0, y: 10, z: 0))
.sheet(isPresented: self.$show, content: { InformationView() })
}
}.frame(width: 246, height: 360)
}
}.padding(30)
Spacer()
}.frame(width: UIScreen.main.bounds.width, height: 480)
Spacer()
}
}
}
To summarize, without ScrollView wrapper the Modal behaviour works as expected.
I would like to know if there is a solution / workaround ? Or I just have to wait a release :)
Edit from answer:
struct HomeList : View {
var listViewItems = listViewItemsData
#State var show = false
#State var view: AnyView = AnyView(Text(""))
var body: some View {
VStack {
HStack {
VStack(alignment: .leading) {
Text("Project title").font(.largeTitle).fontWeight(.heavy)
Text("Project subtitle").foregroundColor(Color.gray)
}
Spacer()
}.padding(.top, 78).padding(.leading, 60)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 30) {
ForEach(listViewItems) { item in
GeometryReader { geometry in
Button(action: {
self.show.toggle()
self.view = item.destination
}) {
ListView(title: item.title, image: item.image, color: item.color, destination: item.destination)
.rotation3DEffect(Angle(degrees: Double((geometry.frame(in: .global).minX - 30) / -30)), axis: (x: 0, y: 10, z: 0))
}
}.frame(width: 246, height: 360)
}
}.padding(30)
Spacer()
}.frame(width: UIScreen.main.bounds.width, height: 480)
.sheet(isPresented: self.$show, content: { self.view })
Spacer()
}
}
}
This is the same issue as https://stackoverflow.com/a/57087399/3179416
Just move your .sheet outside of your ForEach.
import SwiftUI
struct Testing : View {
var listViewItems: [Int] = [1, 2, 3]
#State var show = false
var body: some View {
VStack {
HStack {
VStack(alignment: .leading) {
Text("Project title").font(.largeTitle).fontWeight(.heavy)
Text("Project subtitle").foregroundColor(Color.gray)
}
Spacer()
}.padding(.top, 78).padding(.leading, 60)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 30) {
ForEach(listViewItems, id: \.self) { item in
GeometryReader { geometry in
Button(action: { self.show.toggle()}) {
Text("Button")
.rotation3DEffect(Angle(degrees: Double((geometry.frame(in: .global).minX - 30) / -30)), axis: (x: 0, y: 10, z: 0))
}
}.frame(width: 246, height: 360)
}
}.padding(30)
Spacer()
}.frame(width: UIScreen.main.bounds.width, height: 480)
.sheet(isPresented: self.$show, content: { Text("Modal") })
Spacer()
}
}
}

Resources