LazyVGrid code behaves differently in different Xcode versions? - swift

I was just watching Standord's CS193p (2021 editions)
The instructor shows this as an example of LazyVGrid:
As you see the spacing is equal in every direction.
Now here is how I see it with the exact same code in Xcode 14.2 (it's the same with 9 cards too):
I double checked and the code is exactly the same for both of us!
import SwiftUI
struct ContentView: View {
var emojis = ["✈️", "🚀", "🚑", "🚕", "🚁", "🚜", "🚂", "🛻", "🏍️"]
#State var emojiCount = 8
var body: some View {
VStack {
LazyVGrid(columns: [GridItem(),GridItem(),GridItem(),GridItem()]) {
ForEach(emojis[0..<emojiCount], id: \.self) { emoji in
CardView(content: emoji).aspectRatio(2/3, contentMode: .fit)
}
}
.foregroundColor(.red)
Spacer()
HStack {
remove
Spacer()
add
}
.font(.largeTitle)
.padding(.horizontal)
}
.padding(.horizontal)
}
var remove: some View {
Button {
if emojiCount > 1 {
emojiCount -= 1
}
} label: {
Image(systemName: "minus.circle")
}
}
var add: some View {
Button {
if emojiCount < emojis.count {
emojiCount += 1
}
} label: {
Image(systemName: "plus.circle")
}
}
}
struct CardView: View {
var content: String
#State var isFaceUp: Bool = true
var body: some View {
ZStack {
let shape = RoundedRectangle(cornerRadius: 25)
if isFaceUp {
shape.fill(.white)
shape.stroke(lineWidth: 3)
Text(content).font(.largeTitle)
} else {
shape.fill(.red)
}
}
.onTapGesture {
isFaceUp = !isFaceUp
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
ContentView()
.preferredColorScheme(.dark)
}
}
How is that possible? if Apple just changes the behavior like that, what happens to old apps when running in newer devices?
Also, how can I set the vertical spacing to be exactly the same as horizonal spacing? (I am aware I can set the vertical spacing like this: LazyVGrid(columns:..., spacing: 10), but that is a fixed amount and not always equal to horizontal spacing depending on the cols)
Timed link to the video on Youtube
Edit:
Funny enough, removing the Text() inside card or adding .padding() to it fixes the issue, but I still don't understand why, it is more confusing when you watch the video:
Link to Video

Related

Navigation stack yellow warning triangle

I'm attempting to listen for a change in a boolean value & changing the view once it has been heard which it does successfully, however, results in a yellow triangle. I haven't managed to pinpoint the issue but it doesn't seem to have anything to do with the view that it's transitioning to as even when changed the error still persists.
My code is below
import SwiftUI
struct ConversationsView: View {
#State var isShowingNewMessageView = false
#State var showChat = false
#State var root = [Root]()
var body: some View {
NavigationStack(path: $root) {
ZStack(alignment: .bottomTrailing) {
ScrollView {
LazyVStack {
ForEach(0..<20) { _ in
Text("Test")
}
}
}.padding()
}
Button {
self.isShowingNewMessageView.toggle()
} label: {
Image(systemName: "plus.message.fill")
.resizable()
.renderingMode(.template)
.frame(width: 48, height: 48)
.padding()
.foregroundColor(Color.blue)
.sheet(isPresented: $isShowingNewMessageView, content: {
NewMessageView(show: $isShowingNewMessageView, startChat: $showChat)
})
}
}
.onChange(of: showChat) { newValue in
guard newValue else {return}
root.append(.profile)
}.navigationDestination(for: Root.self) { navigation in
switch navigation {
case .profile:
ChatView()
}
}
}
enum Root {
case profile
}
}
ChatView() Code:
import SwiftUI
struct ChatView: View {
#State var messageText: String = ""
var body: some View {
VStack {
ScrollView {
VStack(alignment: .leading, spacing: 12) {
ForEach(MOCK_MESSAGES) { message in
MessageView(message: message)
}
}
}.padding(.top)
MessageInputView(messageText: $messageText)
.padding()
}
}
}
Any support is much appreciated.
You should use navigationDestination modifier inside your NavigationStack component, just move it.
NavigationStack(path: $root) {
ZStack(alignment: .bottomTrailing) {
ScrollView {
LazyVStack {
ForEach(0..<20) { _ in
Text("Test")
}
}
}.padding()
}.navigationDestination(for: Root.self) { navigation in
switch navigation {
case .profile:
ChatView()
}
}
//...
}
Basically this yellow triangle means NavigationStack can't find suitable component for path. And when you using navigationDestination directly on NavigationStack View or somewhere outside it is ignored
You must set .environmentObject(root) to NavigationStack in order to provide the NavigationPath to the view subhierarchy (ChatView in your case). Also you must have a #EnvironmentObject property of type Root in your ChatView so that it can read the path.

Taking Top Space When Adding Tab Bar in SwiftUI

I am integrating Side Menu in my SwiftUI app. The side menu is working fine. The problem I am getting is I have a tab bar at the bottom, and when I open my side menu screen, it is taking some space from the top and I am not getting why it is getting that space. This is what I am getting:
And when I open Debug View Hierarchy, it is showing me this:
This is my code for my home screen:
struct HomeView: View {
#State var menuOpen: Bool = false
var body: some View {
Zstack {
ZStack {
HStack {
if !self.menuOpen {
Button {
print("side menu tapped")
self.menuOpen.toggle()
} label: {
Image("sideMenuButton")
.resizable()
.frame(width: 24, height: 24)
}.padding()
}
Spacer()
}
Text("Home")
.font(.custom(Poppins.semiBold.rawValue, size: 17))
.foregroundColor(Color(ColorName.appBlue.rawValue))
}
ZStack {
SideMenu(width: 300,
isOpen: self.menuOpen,
menuClose: self.openMenu)
}
}
}.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
func openMenu() {
self.menuOpen.toggle()
}
}
This is working fine when I use it without tab bar. Without tab bar it looks like this:
My code for tab bar controller is:
struct TabBarControllerView: View {
#State private var tabSelection = 0
var body: some View {
TabView(selection: $tabSelection) {
HomeView()
.tabItem {
Label("Home", image: tabSelection == 0 ? ImageName.home.rawValue : ImageName.silverHome.rawValue)
}
.tag(0)
MyAccountView()
.tabItem {
Label("Claims", image: tabSelection == 1 ? ImageName.claims.rawValue : ImageName.silverClaims.rawValue)
}
.tag(1)
}.accentColor(Color(ColorName.appBlue.rawValue))
}
}
struct TabBarControllerView_Previews: PreviewProvider {
static var previews: some View {
TabBarControllerView()
}
}
Does anyone knows why is it getting the top space? I tried .ignoresSafeArea() and also gave padding in negative, it went to top but the side menu did not tapping because it is getting under that space.
I am been stuck in this for 2 days, any help will be appreciated.
TabView comes with Navigation Bar up top. There is a simple solution by adding .navigationBarHidden(true).
See this post: SwiftUI how to hide navigation bar with TabView
Note: Also in the future give a proper minimum amount of code to reproduce the behavior. Your current code given relies on other components and isn't runnable by itself on a fresh project.
Update:
I created a minimum project to test this and this code works fine with me:
import SwiftUI
#main
struct testApp: App {
var body: some Scene {
WindowGroup {
TabBarView()
}
}
}
struct TabBarView: View {
#State private var tabSelection = 0
var body: some View {
TabView(selection: $tabSelection) {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
.tag(0)
}
}
}
struct HomeView: View {
var body: some View {
ZStack {
Rectangle().foregroundColor(.green)
VStack {
HStack {
Spacer()
Button {
print("side menu tapped")
} label: {
Image(systemName: "pencil")
.resizable()
.frame(width: 24, height: 24)
}.padding()
Spacer()
}
Spacer()
}
}
}
}
Given that this code works fine and the issue is not here, minimum working code sample is needed from OP.

List clipping issue (bug with SwiftUI?)

I'm having this weird issue with the List in macOS Montery Beta 3, where some of the list's items are being clipped, and you have to scroll up and back down to unclip it.
Example:
https://imgur.com/a/P0TUt85
Reproduction
Create a new blank SwiftUI macOS project
Paste the following code:
struct Bla: Identifiable {
var text: String
var subtext: String
var id = UUID()
}
struct ContentView: View {
#State var data = (0..<100).map { Bla(text: "Text: \($0)", subtext: "Subtext: \($0)")}
var body: some View {
NavigationView {
List(data) { item in
VStack(alignment: .leading) {
Text(item.text)
Text(item.subtext)
.foregroundColor(.secondary)
}
}
.toolbar {
Button(action: {
var offset = 0
for i in 0..<data.count {
if i % 2 == 0 {
continue
}
data.remove(at: i - offset)
offset += 1
}
}) {
Image(systemName: "plus")
}
}
}
}
}
Run the app, and scroll the list to the middle.
Press the plus button on the toolbar. This simply removes half of the items.
You should see the clipping issue. Scrolling up and back down fixes it.
Is this something with my code, or with SwiftUI's List?
After adding the .listStyle I dont see the clipping issue anymore, try this:
List(data) { item in
VStack(alignment: .leading) {
Text(item.text)
Text(item.subtext)
.foregroundColor(.secondary)
}
}.listStyle(SidebarListStyle())

is it possible get List array to load horizontally in swiftUI?

Do I need to dump using List and just load content into a Scrollview/HStack or is there a horizontal equivalent to stack? I would like to avoid having to set it up differently, but am willing todo so if there is no alternative... it just means recoding multiple other views.
current code for perspective:
import SwiftUI
import Combine
struct VideoList: View {
#Environment(\.presentationMode) private var presentationMode
#ObservedObject private(set) var viewModel: ViewModel
#State private var isRefreshing = false
var btnBack : some View { Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image("Home") // set image here
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
}
}
}
var body: some View {
NavigationView {
List(viewModel.videos.sorted { $0.id > $1.id}, id: \.id) { video in
NavigationLink(
destination: VideoDetails(viewModel: VideoDetails.ViewModel(video: video))) {
VideoRow(video: video)
}
}
.onPullToRefresh(isRefreshing: $isRefreshing, perform: {
self.viewModel.fetchVideos()
})
.onReceive(viewModel.$videos, perform: { _ in
self.isRefreshing = false
})
}
.onAppear(perform: viewModel.fetchVideos)
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: btnBack)
}
}
In general, List is List and it by design is vertical-only. For all horizontal case we should use ScrollView+HStack or ScrollView+LazyHStack (SwiftUI 2.0).
Anyway here is a simple demo of possible way that can be applicable in some particular cases. Prepared & tested with Xcode 12 / iOS 14.
Note: all tuning and alignments fixes are out of scope - only possibility demo.
struct TestHorizontalList: View {
let data = Array(1...20)
var body: some View {
GeometryReader { gp in
List {
ForEach(data, id: \.self) {
RowDataView(item: $0)
.rotationEffect(.init(degrees: 90)) // << rotate content back
}
}
.frame(height: gp.size.width) // initial fit in screen
.rotationEffect(.init(degrees: -90)) // << rotate List
}
}
}
struct RowDataView: View {
let item: Int
var body: some View {
RoundedRectangle(cornerRadius: 25.0).fill(Color.blue)
.frame(width: 80, height: 80)
.overlay(
Text("\(item)")
)
}
}

TabbedView using SwiftUI in Xcode11Beta (11M336w)

I am following along with the session from WWDC2019 here :
https://developer.apple.com/videos/play/wwdc2019/216/
I have the following code working for creating a TabbedView using SwiftUI :
//Section1 | ContentView (mine)---------------------------
import SwiftUI
struct ContentView : View {
var body: some View {
NavigationView {
TabbedView(selection: .constant(1)) {
PlaceForm().tabItemLabel(Text("Tab1")).tag(1)
FavoritesForm().tabItemLabel(Text("Tab2")).tag(2)
}
}
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
//---------------------------
The above produces the following tabbed view :
However, in the WWDC2019 session, the following code is used :
//Section2 | ContentView (Apple's)---------------------------
import SwiftUI
struct ContentView : View {
var body: some View {
NavigationView {
TabbedView(selection: .constant(1)) {
PlaceForm().tabItemLabel {
Image(systemName: "square.and.pencil")
Text("Tab1")
}
FavoritesForm().tabItemLabel {
Image(systemName: "clock.fill")
Text("Tab2")
}
}
}
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
//---------------------------
However, on Xcode11Beta, this results in the following compiler error being thrown by Xcode11Beta
Cannot convert value of type 'TabbedView<Int,
TupleView<(_ModifiedContent<PlaceForm, _TraitWritingModifier<AnyView?>>,
_ModifiedContent<FavoritesForm, _TraitWritingModifier<AnyView?>>)>>' to
closure result type '_'
as seen in the following screenshots
and
//---------------------------
What is the reason that the code demonstrated in the WWDC2019 slides don't result in the images showing up in the tabs of the tabbed view as should be expected if the information in the WWDC2019 presentation is correct?
Also, with the code in section1, switching tabs to tab2 shows a blank view as described in the following question :
SwiftUI TabbedView only shows first tab's content
Please note that the contents of PlaceForm and FavoritesForm are as reproduced below
//Section3 | PlaceForm---------------------------
import SwiftUI
struct PlaceForm : View {
var body: some View {
List {
VStack {
MapView()
.edgesIgnoringSafeArea(.top)
.frame(height: 300)
CircleImage()
.offset(y: -130)
.padding(.bottom, -130)
VStack {
VStack {
Text("Turtle Rock")
.font(.title)
.color(.black)
}
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
}
}.listStyle(.grouped)
}
}
#if DEBUG
struct PlaceForm_Previews : PreviewProvider {
static var previews: some View {
PlaceForm()
}
}
#endif
//Section4 | FavoritesForm---------------------------
import SwiftUI
struct FavoritesForm : View {
var body: some View {
List {
VStack {
MapView()
.edgesIgnoringSafeArea(.top)
.frame(height: 300)
CircleImage()
.offset(y: -130)
.padding(.bottom, -130)
VStack {
VStack {
Text("Ninja Rock")
.font(.title)
.color(.black)
}
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
}
}.listStyle(.grouped)
}
}
#if DEBUG
struct FavoritesForm_Previews : PreviewProvider {
static var previews: some View {
FavoritesForm()
}
}
#endif
This issue was fixed with Xcode 11 beta 3. From iOS & iPadOS 13 Beta 3 Release Notes:
The tabItemLabel(:) modifier — now named tabItem(:) — now accepts
#ViewBuilder closures. (51502668)
Example:
myView()
.tabItem {
Image(systemName: "circle")
Text("Tab1")
}
I had success in Beta 2 by wrapping the 2 controls inside tabItemLabel with a VStack:
.tabItemLabel(VStack {
Image(systemName: "list.bullet")
Text("Foo").font(.title)
})
After some playing around, it looks like tabbed views don't accept system images yet. This code compiled for me. I'm running Xcode 11.0 beta (11M336w) on Catalina 10.15 Beta (19A487m).
struct TabView : View {
var body: some View {
TabbedView {
HomeFeedUIV().tabItemLabel(Image(systemName: "house")) // doesn't work
DatabaseHomeUIV().tabItemLabel(Image("database.unselected")) // works
NewPostUIV().tabItemLabel(Image(systemName: "square.and.pencil")) // doesn't work
}
}
}
I tried using a VStack for the tabItemLabels (Image and Text) but the debugger said tabItemLabels don't accept VStacks, only Images and text. I have yet to find out how to make text and and image appear, it seems to only accept one or the other. I've tried using parenthesis, brackets, curly braces, VStacks, none of them work. Looks like it's one or the other for now.