SwiftUI - Ugly Animation switching from portrait to landscape - swiftui

I have this app for iPhone and I would like to have two views, one for portrait and one for landscape. So I have this on ContentView:
struct ContentView: View {
var body: some View {
Group {
GeometryReader { geometry in
if (geometry.size.width > geometry.size.height) {
ZStack {
Color.red
.edgesIgnoringSafeArea(.all)
Text("LANDSCAPE")
.font(.title)
.foregroundColor(.white)
}
}
if (geometry.size.width < geometry.size.height) {
ZStack {
Color.blue
.edgesIgnoringSafeArea(.all)
Text("PORTRAIT")
.font(.title)
.foregroundColor(.white)
}
}
}
}
.background(Color.black.edgesIgnoringSafeArea(.all))
}
}
When the iPhone rotates, I have this ugly animation:
I don't like the views coming from the edge and rotating like that.
Any way to make like the view is in place and just change colors and text?
Any way to improve this and create something more beautiful?

Here is possible solution - just changed combination of used containers.
Tested with Xcode 12.1 / iOS 14.1
struct ContentView: View {
var body: some View {
GeometryReader { gp in
ZStack {
LANDSCAPEView().opacity(gp.size.width > gp.size.height ? 1.0 : 0)
PORTRAITView().opacity(gp.size.width < gp.size.height ? 1.0 : 0)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.animation(.default)
.background(Color.black.edgesIgnoringSafeArea(.all))
}
}
struct LANDSCAPEView: View {
var body: some View {
Color.red
.overlay(
Text("LANDSCAPE")
.font(.title)
.foregroundColor(.white)
)
.edgesIgnoringSafeArea(.all)
}
}
struct PORTRAITView: View {
var body: some View {
Color.blue.overlay(
Text("PORTRAIT")
.font(.title)
.foregroundColor(.white)
)
.edgesIgnoringSafeArea(.all)
}
}

Related

move content by scroll when overlay is presented

I created some MRE:
struct ContentView: View {
#State private var presentOverlay: Bool = false
var body: some View {
GeometryReader { geo in
List {
Section {
VStack(alignment: .center) {
Spacer()
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
.onTapGesture {
presentOverlay.toggle()
}
Text("Hello, world!")
}
.frame(height: geo.size.height * 0.8)
} header: {
Text("Header")
} footer: {
Text("Footer")
}
}
.overlay(alignment: .bottom) {
if presentOverlay {
Text("Some overlay")
.frame(width: geo.size.width, height: geo.size.height * 0.2)
.background(.yellow)
.transition(.move(edge: .bottom))
}
}
}
.animation(.default, value: presentOverlay)
}
}
Goal is to, when overlay appears, move content of list, so Image can be presented and clickable (currently overlay is going above it).
Is it possible to move content like it is scrolled using List component?

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.

How to center a second HStack View?

I'm having trouble centering that second "Date" Text within the HStack. As you can see, in the image, it is a bit farther to the left. I want only the second view to be centered in the HStack. I want the first View to be latched to leading.
Here is the code.
import SwiftUI
struct DaySummariesBarChart: View {
var body: some View {
HStack {
Text("Date")
.font(.footnote)
Text("Date")
Spacer()
}
}
}
struct BarChart_Previews: PreviewProvider {
static var previews: some View {
DaySummariesBarChart()
.previewLayout(.sizeThatFits)
}
}
This is a pretty clean way to do it:
var body: some View {
ZStack {
Text("Date")
.font(.footnote)
.frame(maxWidth: .infinity, alignment: .leading)
Text("Date")
}
}
The first Text gets a maxWidth of infinity, so it takes up the whole space, but is aligned to .leading.
The second Text is centered by default in the ZStack.
The Spacer() moves the Views to the left. Your problem should be solved by adding another Spacer() on the other side.
struct DaySummariesBarChart: View {
var body: some View {
HStack {
Spacer()
Text("Date")
.font(.footnote)
Text("Date")
Spacer()
}
}
}
You can use a GeometryReader to make the width of the Views exactly half and therefore center the second one.
struct DaySummariesBarChart: View {
var body: some View {
GeometryReader { geometry in
HStack {
Text("Date")
.font(.footnote)
.frame(width: geometry.size.width / 2)
Text("Date")
.frame(width: geometry.size.width / 2)
}
}
}
}

Is there a way to keep a view always visible in ZStack in SwiftUI?

I have a view that I'd like to completely cover at some point and I'd like just one particular child view not to be covered. Is this possible in SwiftUI?
See this code for example:
import SwiftUI
#main
struct ZIndexExperimentApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var showCover = false
var body: some View {
ZStack {
VStack {
Spacer()
Text("Normal text")
.font(.headline)
.padding()
Text("Text that should always be visible")
.font(.headline)
.padding()
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
if self.showCover {
VStack {
Rectangle()
.fill(Color.blue)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.edgesIgnoringSafeArea(.all)
}
}
.onAppear(perform: {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.showCover = true
}
})
}
}
Is there a way to make the second Text to be on top of the cover? I tried to set the zindex on it to a high value but it didn't seem to have an effect.
I think using the foregroundColor or hidden is the better way, because if you kill some Views in your screen you would be notice some displacement on View which is not pleasant for user.
Version 1:
import SwiftUI
struct ContentView: View {
#State var showCover: Bool = Bool()
var body: some View {
ZStack {
if showCover { Color.blue }
VStack {
Spacer()
Text("Normal text")
.foregroundColor(showCover ? Color.clear : Color.primary)
.padding()
Text("Text that should always be visible")
.padding()
Spacer()
}
}
.font(.headline)
.ignoresSafeArea()
.onAppear() { DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { showCover = true } }
}
}
Version 2:
import SwiftUI
struct ContentView: View {
#State var showCover: Bool = Bool()
var body: some View {
ZStack {
if showCover { Color.blue }
VStack {
Spacer()
if showCover { Text("Normal text").padding().hidden() }
else { Text("Normal text").padding() }
Text("Text that should always be visible")
.padding()
Spacer()
}
}
.font(.headline)
.ignoresSafeArea()
.onAppear() { DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { showCover = true } }
}
}

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)
}