App Store Collection View Layout in swiftUI - swiftui

I'm planning to add a collection view like you can see in apple musics "browse" or the app store "apps" tab into my SwiftUI App. I tried with scroll view and stacks but it does not have this smooth movement effect. I figured that you could use NSCollectionView Layout and Diffiable Data Source in UIKit for this but I have no idea how to make it in SwiftUI. Any thoughts?
Heres is my code:
struct DiscoverPopularUsersViewPhone: View {
// MARK: Variables and Constants
#State private var fetchedUserAccounts: [UserAccount] = [UserAccount]()
var geometry: GeometryProxy
#State private var userResultsLoadState: LoadState = .inactive
// MARK: Body
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Divider()
.padding(.horizontal, 18)
HStack(spacing: 16) {
Text("Popular Users")
.font(Font.title3.weight(.bold))
.foregroundColor(Color.primary)
Spacer()
NavigationLink(destination: Text("Popular Users")) {
Text("View more")
.font(Font.callout.weight(.regular))
}
}
.padding(.horizontal, 18)
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 12) {
ForEach(fetchedUserAccounts) { account in
DiscoverUserGridItem(userAccount: account, size: (geometry.size.width - 18 * 2 - 12) / 2)
}
}
.padding(.horizontal, 18)
}
}
.onAppear(perform: fetchPopularUsers)
}
}
The fetchPopularUsers method just fetches some user data
I am aware that there is not really a way to do this and it is not an error. But I want it a bit different even if this means I need a whole new approach.
You can see the snappy effect I want to achieve at the top of the App Store app. I could not include a video because it would be too large
Note: I would like the user to see that he is able to scroll so I think the PageTabViewStyle() modifier of TabView is also not an option

Related

Some NavigationLink are inaccessible

My SwiftUI TVOS app has two sets of NavigationLink. When both sets are present (not commented out), only one set is accessible to tap on. If I comment out one or the other set, the remaining NavigationLink is accessible to tap on and functions properly.
How can both sets of NavigationLink be accessible (can be interacted with)?
I've tried encapsulating my view in NavigationView and NavigationStack, neither behaved differently.
The view, as shown below, only the NavigationLinks in the ScrollView are accessible to interact with. The "Edit" NavigationLink cannot be selected to tap on. If I comment out the ScrollView NavigationLinks, then the "Edit" NavigationLink becomes accessible and functions correctly.
I've also tried replacing LazyVGrid with VStack to no effect.
import SwiftUI
struct TestSources: Hashable {
let id = UUID()
let name: String
}
struct SourcesView: View {
private var Sources = [TestSources(name: "Computer 1"), TestSources(name: "Computer 2")]
var columns: [GridItem] {
Array(repeating: .init(.adaptive(minimum: 200)), count: 2)
}
var body: some View {
NavigationStack {
VStack(alignment: .center) {
// Header
HStack(alignment: .center){
Label("Sources", systemImage: "externaldrive.connected.to.line.below")
.font(.headline)
.frame(maxWidth: .greatestFiniteMagnitude, alignment: .leading)
.padding(.all)
NavigationLink(destination: TestEditView()) {
Text("Edit")
}
}
Divider()
ScrollView(.vertical, showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(Sources.indices, id: \.self) { index in
NavigationLink(Sources[index].name ,value: Sources[index])
}.navigationDestination(for: TestSources.self) { source in
TestShareView(source: source)
}
.accentColor(Color.black)
.padding(Edge.Set.vertical, 20)
}
.padding(.horizontal)
}
}.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
)
}
}
}
struct TestEditView: View {
var body: some View {
Text("Edit")
}
}
struct TestShareView: View {
let source : TestSources
var body: some View {
Text(source.name)
}
}
I don't see any problem with the navigation links in this code.
I pasted the code into a new project and tweaked it a little to make it compile. As you can see, it just works.
My guess is that it might fail because something outside of this code. Maybe, it is within another NavigationStack or some structure that could increse it's navigation complexity?
Or as Yrb suggests, this force unwrapping could be failing because of null values?

How do you keep 'fullScreenISPresented' from dismissing itself when the screen rotates?

I have a LazyVGrid with a layout count: 2 when in portrait, and court: 3 when in landscape, in a scrollview. I use a ternary to change the count. Problem is when I scroll down than select a cell, when the model slides up and I rotate, the view dismisses by itself. I also notice the scroll seems to be in a totally different location.
Do I need to build this differently? Funny thing is it only happens at certain places down in the scrollview. Its not consistent. Sometimes it works fine then as I continue to scroll down it'll start to happen.
If I don't change the layout count in portrait or landscape, it works fine. It seems change the count causes this.
struct Feed_View: View {
#EnvironmentObject var viewModel : Post_View_Model
#Environment(\.verticalSizeClass) var sizeClass
#FocusState private var isFocused: Bool
var body: some View {
Color("BGColor").ignoresSafeArea()
ZStack {
VStack (alignment: .center, spacing: 0) {
//MARK: - NAVIGATION BAR
NavBar_View() // Top Navigation bar
.frame(maxHeight: 40, alignment: .center)
//MARK: - SCROLL VIEW
ScrollView (.vertical, showsIndicators: false) {
//MARK: - FEED FILL
let layout = Array(repeating: GridItem(.flexible(), spacing: 10), count: sizeClass == .compact ? 3 : 2)
LazyVGrid(columns: layout, spacing: 10) {
ForEach (viewModel.posts, id: \.self) { posts in
Feed_Cell(postModel: posts)
} //LOOP
} //LAZYV
.padding(.horizontal, 10).padding(.vertical, 10)
} //SCROLL
} //V
} //Z
}
}

SwiftUI pop out temporary alert

I tried to do a app that pop out a temporary alert that only appear for 1 or 2 seconds. It’s something like App Store rating.
But I don’t know what this called in swiftui. Can anyone answer me?
That is just a view that is shown or hidden conditionally. Here is a complete example that uses a ZStack to place the thank you view over the other view content. The thank you view is either present or not based upon the #State variable showThankYou. DispatchQueue.main.asyncAfter is used to remove the view after 3 seconds.
struct ContentView: View {
#State private var showThankYou = false
var body: some View {
ZStack {
VStack {
Spacer()
Text("Stuff in the view")
Spacer()
Button("submit") {
showThankYou = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.showThankYou = false
}
}
Spacer()
Text("More stuff in the View")
Spacer()
}
if showThankYou {
RoundedRectangle(cornerRadius: 16)
.foregroundColor(Color.gray)
.frame(width: 250, height: 250)
.overlay(
VStack {
Text("Submitted").font(.largeTitle)
Text("Thanks for your feedback").font(.body)
}
)
}
}
}
}

SwiftUI Navigation View - Handling Size Classes over multiple views

I have the scenario where I intend on using a menu ('MenuView') that varies if the device is in portrait / landscape mode. I am using size classes to determine the device type and this successfully redraws the view on rotation. The menu uses navigation view/links to take you to a further view (the 'DetailedView'); this view also has differing views for portrait and landscape. Again I'm using size classes to successfully redraw the view based on the rotation.
However, what I find is that when I'm in the DetailedView and rotate the device, the display jumps straight back to the MenuView as, of course, it has recognized the size class change and adjusted that view. I would like the display to remain in this view.
How can I prevent the app from jumping to the 'MenuView' when I rotate the device that is displaying the 'DetailedView'? Code from the MenuView below:
Note: I'm using the StackNavigationViewStyle in this instance.
Any help would be gratefully received, thanks in advance!
struct MenuView: View {
#Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
#Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
var body: some View {
NavigationView {
Color("MainBg")
.edgesIgnoringSafeArea(.all)
if horizontalSizeClass == .compact {
PortraitMenuView()
} else {
LandscapeMenuView()
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
Update: DetailedView - This is called from the PortraitMenuView and LandscapeMenuView. Included code from PortraitMenuView - the same navigation link is included in the LandscapeMenuView
struct DetailedView: View {
#Environment(\.horizontalSizeClass) var sizeHClass
#Environment(\.verticalSizeClass) var sizeVClass
var formulae: Functions
var body: some View {
ZStack {
Color.offWhite
.edgesIgnoringSafeArea(.all)
VStack {
Group {
if sizeHClass == .compact && sizeVClass == .compact {
DetailedViewLandscape()
} else {
DetailedViewPortrait()
}
}
}
}
}
}
struct PortraitMenuView: View {
var body: some View {
VStack {
ZStack {
Circle()
.fill(Color("kMainBg"))
.frame(width: 300, height: 300)
.overlay(
Text("FORMULA FINDER")
.font(.largeTitle).bold()
.multilineTextAlignment(.center)
.minimumScaleFactor(0.005)
.lineLimit(2)
.frame(width: 210, height: 210)
)
}
Spacer()
NavigationLink(destination: DetailedView()) {
TileView(title: "Formulae", subtitle: "Functions and formulas", boxColor: Color.pastelGreen)
}.offset(x: 40)
}.padding(.bottom, 20)
}
}

SwiftUI - how to save settings selections

import SwiftUI
struct CardTheme: View {
//#State private var theme = 0
#State private var theme = UserDefaults.standard.integer(forKey: "Card Theme")
var body: some View {
List {
HStack {
Text("Mono")
//.font(.system(size: 12))
.onTapGesture {
self.setTheme(i: 0)
}
Spacer()
if(theme == 0) {
Image(systemName: "checkmark")
.foregroundColor(Color.green)
}
}
HStack {
Text("Cool")
// .font(.system(size: 12))
.onTapGesture {
self.setTheme(i: 1)
}
Spacer()
if(theme == 1) {
Image(systemName: "checkmark")
.foregroundColor(Color.green)
}
}
HStack {
Text("Cute")
// .font(.system(size: 12))
.onTapGesture {
self.setTheme(i: 2)
}
Spacer()
if(theme == 2) {
Image(systemName: "checkmark")
.foregroundColor(Color.green)
}
}
}
.navigationBarTitle(Text(verbatim: "Card Theme"))
}
func setTheme(i: Int) {
theme = i
UserDefaults.standard.set(i, forKey: "Card Theme")
}
}
I have a settings menu where the user picks a theme, the default value is set to a global variable, globalVarTheme, which is 0. But after they make a selection, exit that menu, and re-enter the menu it goes back to 0 (the first item) even if they have chosen one of the other items. How do I save their selection?
Also, what is the best way to save user selections beyond the current app session? Should I write all their selections to a plist file or is there a conventional way?
#State is used for the changes within a given view. It is not meant to persist across the views. Instead use #Environment property wrapper. WWDC 2019 talks about that when to use what.
#State isn't the right PropertyWrapper.
If you want to use your settings in multiple views than use #EnvironmentObject as the PropertyWrapper.
You can read about the different PropertyWrappers here:
https://medium.com/#alex.hsieh/state-objectbinding-and-environmentobject-in-swiftui-783588b60671
If you want to save the Settings beyond the current app session you can use UserDefauls.
How do I use UserDefaults with SwiftUI?