Matched Geromety transition not "smooth" - swiftui

I am trying to use Matched Geometry to transition from one view to another. I've attached the video, as you can see, it's not "smooth". There is a fade on the left and right side of the burger as it transitions. What I doing wrong?
struct FullView: View {
var namespace: Namespace.ID
var body: some View {
VStack {
Image("b4")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: getRect().width / 1.5)
.matchedGeometryEffect(id: "burger", in: namespace)
Text("Baconator")
.font(.title)
.bold()
.matchedGeometryEffect(id: "title", in: namespace)
Text("The Baconator sandwich is a cheeseburger sold by the international fast-food restaurant chain Wendy's.")
.font(.caption)
.padding(.vertical)
Spacer()
}
.padding()
}
}
struct MiniView: View {
var namespace: Namespace.ID
var body: some View {
VStack {
Image("b4")
.resizable()
.aspectRatio(contentMode: .fit)
.matchedGeometryEffect(id: "burger", in: namespace)
Text("Baconator")
.font(.title)
.bold()
.matchedGeometryEffect(id: "title", in: namespace)
}
}
}
struct ContentView: View {
#State var showingFull = false
#Namespace private var namespace
var body: some View {
ZStack {
Color.black.opacity(0.2)
.ignoresSafeArea()
if (showingFull) {
FullView(namespace: namespace)
} else {
MiniView(namespace: namespace)
.frame(width: getRect().width / 3)
}
}
.onTapGesture {
withAnimation(.spring()) {
showingFull.toggle()
}
}
}
}
Animation video link: https://streamable.com/tg2eij

Related

Keep VStack in center of the screen in SwiftUI

I'm new on SwiftUI and I don't know how to manage my views.
I have this code:
import SwiftUI
struct ContentView: View {
#State private var email: String = ""
#State private var passord: String = ""
var body: some View {
ZStack {
Image("corner")
.resizable()
.scaledToFill()
VStack {
VStack { //VStack1
TextField("Email", text: $email)
.frame(width: 300, height: 40)
.textFieldStyle(.roundedBorder)
.bold(true)
SecureField("Password", text: $passord)
.frame(width: 300, height: 40)
.textFieldStyle(.roundedBorder)
.bold(true)
Button {
//Do something
} label: {
Text("Forgot your password ?")
.underline()
.foregroundColor(.white)
}
}
VStack { // VStack2
Text("Not registered ?")
.font(.title2)
.foregroundColor(.white)
Button("Sign up") {}
.frame(width: 200, height: 37)
.foregroundColor(.white)
.background(Color(.orange))
.cornerRadius(20)
}
}
}
}
}
I want to place the VStack2 in the bottom of the screen and keep the VStack1 on the center of the screen.
How I can do that. I've try to search but I don't find the solution on StackOverflow.
I tried to play with Spacer() and padding() but I have not a good result.
Screen
A simple way to do this would be to add VStack1 to the ZStack which will place it in the centre of the screen. Then add wrap VStack2 in another VStack with a Spacer to push it to the bottom of the screen, e.g.
ZStack {
Image("corner")
VStack { //VStack1
// etc
}
VStack {
Spacer()
VStack { // VStack2
// etc
}
}
}
Simple put
HStack {
Spacer()
VStack {
Text("Centered")
}
Spacer()
}
import SwiftUI
struct ContentView: View {
#State private var email: String = ""
#State private var passord: String = ""
var body: some View {
VStack {
Spacer()
middleView()
Spacer()
bottomView()
.paddind(.bottom, 12)
}
.ignoresSafeArea()
.background {
Image("corner")
.resizable()
.scaledToFill()
}
}
func middleView() -> some View {
VStack(spacing: 20) {
TextField("Email", text: $email)
.frame(width: 300, height: 40)
.textFieldStyle(.roundedBorder)
.bold(true)
SecureField("Password", text: $passord)
.frame(width: 300, height: 40)
.textFieldStyle(.roundedBorder)
.bold(true)
Button {
//Do something
} label: {
Text("Forgot your password ?")
.underline()
.foregroundColor(.white)
}
}
}
func bottomView() -> some View() {
VStack {
Text("Not registered ?")
.font(.title2)
.foregroundColor(.white)
Button("Sign up") {}
.frame(width: 200, height: 37)
.foregroundColor(.white)
.background(Color(.orange))
.cornerRadius(20)
}
}
}

On appear doesn't trigger every time

I have problem because on appear modifier doesn't call every time when I enter the screen and it calls randomly (when on appear is applied to navigation view). When I put it on some view inside navigation view it never calls. What is the problem, I can't solve it, this is very strange behaviour. This is the view:
struct CheckListView: View {
#EnvironmentObject var appState: AppState
#StateObject var checkListViewModel = CheckListViewModel()
//#State var didSelectCreateNewList = false
#State var didSelectShareList = false
var body: some View {
NavigationView {
GeometryReader { reader in
VStack {
Spacer()
.frame(height: 30)
Text("\(appState.hikingTypeModel.name)/\(appState.lengthOfStayType.name)")
.foregroundColor(Color("rectBackground"))
.multilineTextAlignment(.center)
.font(.largeTitle)
Spacer()
.frame(height: 40)
List {
ForEach(checkListViewModel.checkListModel.sections) { section in
CheckListSection(model: section)
.listRowBackground(Color("background"))
}
}
.listStyle(.plain)
.clearListBackground()
.clipped()
Spacer()
.frame(height: 40)
HStack {
FilledRectangleBorderButtonView(titleLabel: "Share", backgroundColor: Color("rectBackground"),foregroundColor: .white, height: 55, didActionOnButtonHappened: $didSelectShareList)
Spacer()
FilledRectangleBorderButtonView(titleLabel: "Create new list", backgroundColor: .clear, foregroundColor: Color("rectBackground"), height: 55, didActionOnButtonHappened: $checkListViewModel.didSelectNewList)
.onChange(of: checkListViewModel.didSelectNewList) { _ in
UserDefaultsHelper().emptyUserDefaults()
appState.moveToRootView = .createNewList
}
}
.padding([.leading, .trailing], 20)
.frame(width: reader.size.width)
Spacer()
}
.frame(width: reader.size.width, height: reader.size.height)
.background(Color("background"))
.navigationBarStyle()
}
}
.onAppear {
self.checkListViewModel.loadJSON(hikingTypeId: appState.hikingTypeModel.id, lengthStayId: appState.lengthOfStayType.id)
}
}
}

Scrollable content not working as expected in SwiftUI

I'm trying to create a scroll view with my custom view, but when I add scroll to view it's not working as expected, without scroll view working fine.
struct ContentView: View {
var body: some View {
ScrollView(.vertical) {
VStack {
ForEach (0..<2) { _ in
ListItem()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// But the below code is working fine.
struct ContentView: View {
var body: some View {
VStack {
ForEach (0..<2) { _ in
ListItem()
}
}
}
}
// List Item
struct ListItem: View {
var body: some View {
VStack {
HStack {
Image("steve")
.resizable()
.clipShape(Circle())
.aspectRatio(contentMode: .fit)
.frame(maxWidth:44, maxHeight: 44)
VStack {
Text("Steve Jobs")
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
Text("1 hour ago")
.font(.footnote)
.frame(maxWidth: .infinity, alignment: .leading)
}
Spacer()
}
ZStack(alignment:.top) {
GeometryReader { geometry in
VStack {
ZStack {
Image("poster_1")
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(8)
.shadow(color: Color.black.opacity(0.12),
radius: 4, x: 1, y: 1)
.frame(width: geometry.size.width - 64,
height: geometry.size.height * 0.35)
.padding([.horizontal], 32)
.clipped()
ZStack {
Rectangle()
.fill(Color.black.opacity(0.75))
.frame(maxWidth:84 , maxHeight: 84)
.cornerRadius(12)
Image(systemName: "play.fill")
.font(.system(size: 44, weight: .bold))
.foregroundColor(.white)
}
}
VStack {
Text("Game of Thrones")
.accentColor(Color.gray.opacity(0.25))
.font(Font.subheadline.weight(.bold))
.padding([.horizontal], 32)
.padding([.bottom], 2)
.frame(maxWidth: .infinity,
alignment: .leading)
VStack {
Text("Game of Thrones is an American fantasy drama television series created by David Benioff and D. B. Weiss for HBO. ")
.accentColor(Color.gray.opacity(0.25))
.font(.footnote)
.frame(maxWidth: .infinity,
alignment: .leading)
.padding([.horizontal], 32)
Text("Show more...")
.accentColor(Color.gray.opacity(0.01))
.font(Font.footnote.weight(.bold))
.frame(maxWidth: .infinity,
alignment: .trailing)
.padding([.trailing], 32).onTapGesture {
print("okay")
}
}
}
}
}
}
}
}
}
ListItem contains multiple views which creates publisher info and movie information as shown below image.
Scrollview is scrolling but images are not shown in view as first image.
It is the geometry reader that you have in ListItem. Because neither a GeometryReader nor a Scrollview have their own size. Since neither no what size to render, they collapse. This is what you are seeing in your view. See this answer. The solution is to put the GeometryReader into ContentView outside the Scrollview and send the GeometryProxy that you called geometry into ListItem something like this:
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
VStack {
ForEach (0..<2) { _ in
ListItem(geometry: geometry)
}
}
} // Scrollview
} // GeometryReader
}
}
struct ListItem: View {
let geometry: GeometryProxy
var body: some View {
...
}
This seems to fix it in Preview, though you may have to change your multipliers in the .frame() that uses geometry to size it how you want.

Cannot remove unwanted space from this VStack

I really cannot figure out what is pushing my Detail HStack View down to the bottom of the page. I've tried adding spacers and other tweaks but no success. You'll find the entire code below, its compile-ready as is.
import SwiftUI
struct ContentView: View {
var body: some View {
GeometryReader { geo in
VStack() {
VideoPlayerView()
.frame(width: geo.size.width, alignment: .center)
CategoryScrollView()
Spacer()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct CategoryScrollView: View {
var body: some View {
HStack {
VStack {
Text("Category")
.font(.system(size: 30))
}
Divider()
VStack {
Text("Chapter")
.font(.system(size: 30))
}
Divider()
VStack {
Text("Lesson")
.font(.system(size: 30))
}
}.frame(height: 100)
}
}
struct CategoryScrollView_Previews: PreviewProvider {
static var previews: some View {
CategoryScrollView()
}
}
struct VideoPlayerView: View {
var body: some View {
GeometryReader { geo in
ZStack(alignment: .center) {
Rectangle()
.frame(height: geo.size.width / 1.4)
.cornerRadius(50)
Image(systemName: "play.fill")
.resizable()
.foregroundColor(.blue)
.frame(width: geo.size.width / 5,height: geo.size.width / 4)
}
}
}
}
struct VideoPlayerView_Previews: PreviewProvider {
static var previews: some View {
VideoPlayerView()
}
}
If unconstrained, the GeometryReader takes all available space.
You need to set the height of the VideoPlayerView as well:
struct ContentView: View {
var body: some View {
GeometryReader { geo in
VStack {
VideoPlayerView()
.frame(width: geo.size.width, height: geo.size.height / 4, alignment: .center)
CategoryScrollView()
Spacer()
}
}
}
}
Also, you can remove frame(height: 100) from the CategoryScrollView:
struct CategoryScrollView: View {
var body: some View {
HStack {
VStack {
Text("Category")
.font(.system(size: 30))
}
Divider()
VStack {
Text("Chapter")
.font(.system(size: 30))
}
Divider()
VStack {
Text("Lesson")
.font(.system(size: 30))
}
}
// .frame(height: 100) // remove this
}
}
If necessary, you can specify this in the parent view using GeometryReader instead of hardcoding values.
I would add this frame to the GeometryReader.
GeometryReader { geo in
VStack() {
VideoPlayerView()
.frame(width: geo.size.width, alignment: .center)
CategoryScrollView()
Spacer()
}.frame(maxHeight:geo.size.height/2)
}

SwiftUI: Show specific views with an animation delay

I'm trying to achieve the following animation: When I tap on a rectangle the rectangle should be expanded to the full width with an close button in the corner and below this rectangle a ScrollView should appear. This works so far without any problems. Now I would like to display the ScrollView a little bit later then the expanded rectangle. So when I tap on the rectangle: First the expanded rectangle with the close button should appear and 3 seconds later the ScrollView.
struct Playground: View {
#Namespace var namespace
#State var show = false
private let gridItems = [GridItem(.flexible())]
var body: some View {
if show {
VStack{
ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)){
Rectangle()
.matchedGeometryEffect(id: "A", in: namespace, isSource: show)
.frame(height: 300)
.frame(maxWidth: .infinity)
Image(systemName: "xmark")
.font(.system(size: 25))
.foregroundColor(.white)
.background(Color.red)
.padding(20)
.onTapGesture {
withAnimation(.spring()){
self.show = false
}
}
}
// SHOW THIS SCROLLVIEW 3 SECONDS LATER
ScrollView{
LazyVGrid(columns: gridItems){
ForEach(0..<10){ cell in
Text("\(cell)")
}
}
}
.animation(Animation.spring().delay(3)) // doesn't work!
}
} else {
Rectangle()
.matchedGeometryEffect(id: "A", in: namespace, isSource: !show)
.frame(width: 100, height: 100)
.onTapGesture {
withAnimation(.spring()){
self.show = true
}
}
}
}
}
We need to make separated animation (and related state) for ScrollView in this scenario.
Here is possible approach. Tested with Xcode 12.1 / iOS 14.1
struct Playground: View {
#Namespace var namespace
#State var show = false
private let gridItems = [GridItem(.flexible())]
#State private var showItems = false
var body: some View {
if show {
VStack{
ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)){
Rectangle()
.matchedGeometryEffect(id: "A", in: namespace, isSource: show)
.frame(height: 300)
.frame(maxWidth: .infinity)
Image(systemName: "xmark")
.font(.system(size: 25))
.foregroundColor(.white)
.background(Color.red)
.padding(20)
.onTapGesture {
withAnimation(.spring()){
self.show = false
}
}
}
VStack {
if showItems {
ScrollView{
LazyVGrid(columns: gridItems){
ForEach(0..<10){ cell in
Text("\(cell)")
}
}
}
} else {
Spacer()
}
}
.onAppear { showItems = true }
.onDisappear { showItems = false }
.animation(Animation.spring().delay(3), value: showItems)
}
} else {
Rectangle()
.matchedGeometryEffect(id: "A", in: namespace, isSource: !show)
.frame(width: 100, height: 100)
.onTapGesture {
withAnimation(.spring()){
self.show = true
}
}
}
}
}