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

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

Related

SwiftUI Is there any built in view that kind of slides in from the side and takes up 3/4 of the screen?

This is probably a custom view but in the Reddit app there's a toolbar and the top left button(3 lines) opens this kind of view from the side that moves the current view to the right so you can only see about 25% of it and a new view that takes up about 75% of the screen slides in. Is there anything like this built into SwiftUI and if there isn't how would I go about implementing something like this?
This is my custom side bar behave similarly to what you just mentioned, you can try it. (Images and Code are below)
Before click:
After clicked:
struct ContentView: View {
#State var isClicked = false
var body: some View {
HStack {
Rectangle()
.fill(.orange)
.frame(width: isClicked ? UIScreen.main.bounds.width * 0.75 : 0)
VStack {
HStack {
Button {
withAnimation {
isClicked.toggle()
}
} label: {
Image(systemName: "menucard.fill")
.padding(.leading)
}
Spacer()
}
Spacer()
}
}
}
}

SwiftUI extend or recalculate VStack height when children views extend

I have a scrollview that holds "cards" with weather details of locations and the cards extend to show more information when tapped.
I have to use a LegacyScrollView so that the bottomsheet that encompasses the scrollview gets dragged down when the the scroll is at the top.
I can't figure out how to extend the VStack when one of the cards extend. Is there a way to make a GeometryReader or VStack recalculate the needed height to hold all of the views?
I start off with the minHeight set to the window size so when the 1st card is extended, it all shows, but 2 or more extend past the LazyVStack window and get cut off.
If the minHeight on the LaxyVStack isn't set to 0, the cells expand from the middle and their tops get cut off. When it's set to 0, the cells expand downward as desired.
GeometryReader { proxy in
LegacyScrollView(.vertical, showsIndicators: false) {
Spacer(minLength: 40)
LazyVStack(spacing: 0) {
ForEach(mapsViewModel.placedMarkers, id: \.id) { _placeObject1 in
InfoCell(placeObject: _placeObject1)
.environmentObject(mapsViewModel)
.frame(alignment: .top)
Divider().foregroundColor(.white)
}
}
.cornerRadius(25)
.frame(minHeight: 0, alignment: .top)
}
.onGestureShouldBegin{ pan, scrollView in
scrollView.contentOffset.y > 0 || pan.translation(in: scrollView).y < 0
}
.onScroll{ test in
print(test.contentOffset.y)
}
.padding(.top, 30) //padding for top of list
}
and the cell's code is :
struct InfoCell: View {
#StateObject var placeObject: PlaceInfo
#State var expandCell = false
var body: some View {
VStack(alignment: .center, spacing: 0) {
//blahblah
if expandCell {
CellExpanded(placeObject: placeObject, isFirstInList: true)
}
}
.frame(maxWidth: .infinity)
.onTapGesture {
withAnimation { expandCell.toggle() }
}
}
}
and the expand code:
struct InfoCellExpanded: View {
#State var forecast = "Daily"
var placeObject: PlaceInfo
var isFirstInList: Bool
var body: some View {
VStack {
//blah
}
.padding(.horizontal, 10)
.padding(.bottom, 20)
}
}

SwiftUI animation not working using animation(_:value:)

In SwiftUI, I've managed to make a Button animate right when the view is first drawn to the screen, using the animation(_:) modifier, that was deprecated in macOS 12.
I've tried to replace this with the new animation(_:value:) modifier, but this time nothing happens:
So this is not working:
struct ContentView: View {
#State var isOn = false
var body: some View {
Button("Press me") {
isOn.toggle()
}
.animation(.easeIn, value: isOn)
.frame(width: 300, height: 400)
}
}
But then this is working. Why?
struct ContentView: View {
var body: some View {
Button("Press me") {
}
.animation(.easeIn)
.frame(width: 300, height: 400)
}
}
The second example animates the button just as the view displays, while the first one does nothing
The difference between animation(_:) and animation(_:value:) is straightforward. The former is implicit, and the latter explicit. The implicit nature of animation(_:) meant that anytime ANYTHING changed, it would react. The other issue it had was trying to guess what you wanted to animate. As a result, this could be erratic and unexpected. There were some other issues, so Apple has simply deprecated it.
animation(_:value:) is an explicit animation. It will only trigger when the value you give it changes. This means you can't just stick it on a view and expect the view to animate when it appears. You need to change the value in an .onAppear() or use some value that naturally changes when a view appears to trigger the animation. You also need to have some modifier specifically react to the changed value.
struct ContentView: View {
#State var isOn = false
//The better route is to have a separate variable to control the animations
// This prevents unpleasant side-effects.
#State private var animate = false
var body: some View {
VStack {
Text("I don't change.")
.padding()
Button("Press me, I do change") {
isOn.toggle()
animate = false
// Because .opacity is animated, we need to switch it
// back so the button shows.
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
animate = true
}
}
// In this case I chose to animate .opacity
.opacity(animate ? 1 : 0)
.animation(.easeIn, value: animate)
.frame(width: 300, height: 400)
// If you want the button to animate when the view appears, you need to change the value
.onAppear { animate = true }
}
}
}
Follow up question: animating based on a property of an object is working on the view itself, but when I'm passing that view its data through a ForEach in the parent view, an animation modifier on that object in the parent view is not working. It won't even compile. The objects happen to be NSManagedObjects but I'm wondering if that's not the issue, it's that the modifier works directly on the child view but not on the passed version in the parent view. Any insight would be greatly appreciated
// child view
struct TileView: View {
#ObservedObject var tile: Tile
var body: some View {
Rectangle()
.fill(tile.fillColor)
.cornerRadius(7)
.overlay(
Text(tile.word)
.bold()
.font(.title3)
.foregroundColor(tile.fillColor == .myWhite ? .darkBlue : .myWhite)
)
// .animation(.easeInOut(duration: 0.75), value: tile.arrayPos)
// this modifier worked here
}
}
struct GridView: View {
#ObservedObject var game: Game
let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 4)
var body: some View {
GeometryReader { geo in
LazyVGrid(columns: columns) {
ForEach(game.tilesArray, id: \.self) { tile in
Button(action: {
tile.toggleSelectedStatus()
moveTiles() <- this changes their array position (arrayPos), and
the change in position should be animated
}) {
TileView(tile: tile)
.frame(height: geo.size.height * 0.23)
}
.disabled(tile.status == .solved || tile.status == .locked)
.animation(.easeInOut(duration: 0.75), value: arrayPos)
.zIndex(tile.status == .locked ? 1 : 0)
}
}
}
}
}

SwiftUI: Spacing between List Items

I have some code that displays a list with a title and a rectangle and if the user taps on it, it navigates to a detail page.
But my code seems to have multiple problems:
The spacer is not working. I want the name on the left and the rectangle on the right like a spacer in a Stack would push it.
The background color of the rectangle is not applied. It is just black
If I add a navigationTitle, I see constraint errors (see below)
(
"<NSLayoutConstraint:0x600002438f50 'BIB_Trailing_CB_Leading' H:[_UIModernBarButton:0x7fd15fe088b0]-(6)-[_UIModernBarButton:0x7fd15fe064d0'World Clock'] (active)>",
"<NSLayoutConstraint:0x600002438fa0 'CB_Trailing_Trailing' _UIModernBarButton:0x7fd15fe064d0'World Clock'.trailing <= _UIButtonBarButton:0x7fd15fe05310.trailing (active)>",
"<NSLayoutConstraint:0x600002439d10 'UINav_static_button_horiz_position' _UIModernBarButton:0x7fd15fe088b0.leading == UILayoutGuide:0x600003e04a80'UIViewLayoutMarginsGuide'.leading (active)>",
"<NSLayoutConstraint:0x600002439d60 'UINavItemContentGuide-leading' H:[_UIButtonBarButton:0x7fd15fe05310]-(0)-[UILayoutGuide:0x600003e049a0'UINavigationBarItemContentLayoutGuide'] (active)>",
"<NSLayoutConstraint:0x6000024350e0 'UINavItemContentGuide-trailing' UILayoutGuide:0x600003e049a0'UINavigationBarItemContentLayoutGuide'.trailing == _UINavigationBarContentView:0x7fd15fd0a640.trailing (active)>",
"<NSLayoutConstraint:0x60000243a4e0 'UIView-Encapsulated-Layout-Width' _UINavigationBarContentView:0x7fd15fd0a640.width == 0 (active)>",
"<NSLayoutConstraint:0x6000024354a0 'UIView-leftMargin-guide-constraint' H:|-(0)-UILayoutGuide:0x600003e04a80'UIViewLayoutMarginsGuide' (active, names: '|':_UINavigationBarContentView:0x7fd15fd0a640 )>"
)
Here is my code:
var body: some View {
NavigationView {
List {
ForEach(viewModel.cityList, id: \.name) { city in
NavigationLink(
destination: CityDetailView(city: city)){
HStack{
Text(city.name)
Spacer()
CustomView()
}
}.frame(height: 100)
}
}
.navigationTitle("World Clock")
}
}
CustomView:
var body: some View {
GeometryReader { geo in
Circle().frame(width: geo.size.height, height: geo.size.height)
.background(Color.green)
}
}
Any idea what is wrong?
Your spacer doesn't work because there's NavigationLink in the HStack which took all the space: it has bigger priority than Spacer.
If you wanna use NavigationLink with EmptyView, it should be placed in a ZStack with nearby view, but in this case you can simply place content of your cell inside NavigationLink label.
Rectangle is not a view but a Shape, and as with any other Shape its color should be set with foregroundColor, not with background.
GeometryReader always takes all available space, unless you're adding size modifiers to it. It cannot wrap around content by itself. So you can use following trick: add onAppear for an item inside GeometryReader and pass width to state variable, which will then add width modifier to GeometryReader.
struct ContentView: View {
#ObservedObject var viewModel = CityListViewModel()
var body: some View {
NavigationView {
List {
ForEach(viewModel.cityList, id: \.name) { city in
NavigationLink(
destination: Text(city.name)){
HStack{
Text(city.name)
Spacer()
CustomView()
}
}.frame(height: 100)
}
}
.navigationTitle("World Clock")
}
}
}
struct CustomView: View {
#State
var width: CGFloat?
var body: some View {
GeometryReader { geo in
Circle().frame(width: geo.size.height, height: geo.size.height)
.background(Color.green)
.onAppear {
width = geo.size.height
}
}.frame(width: width)
}
}
If you see some NSLayoutConstraint broken which you didn't create, like when using SwiftUI, you can ignore it: it's not your bug.

SwiftUI add border to onSelect horizontal Scrollview image

I tried to make a horizontal scrollview that contained a list of item. when I clicked any of the item, it will show a border(highlight) behind the item. When I press another item, the previous border disappear and the latest clicked item appear the border. It just looks like a selection horizontal scrollview. I have no idea about how to do so.
ScrollView(.horizontal, showsIndicators: false){
LazyHStack{
ForEach(self.staffs.indices, id: \.self){ staff in
VStack{
Image(staff.image)
.resizable()
.frame(width: 100, height: 100)
.clipShape(Circle())
}
.onTapGesture {
print(createBookingJSON.staffs[staff].staffID!)
}
}
}
}
Convert your VStack into a standalone view, then pass a Binding to it that can be read from the Parent View. Your new VStack standalone view needs an OnTapGesture or an Action through a button to toggle it's state. We will make your ForEach a "single selection" list as you request.
NEW View to use inside your ForEach:
struct ItemCell: View {
var item: Item
#Binding var selectedItem: Item?
var body: some View {
VStack{
Image(item.image)
.resizable()
.frame(width: 100, height: 100)
.border(Color.green, width: (item == selectedItem) ? 20 : 0)
}
.onTapGesture {
self.selectedItem = item
print(createBookingJSON.staffs[staff].staffID!)
}
}
}
Now in the view that contains your Foreach, add a State Var of selectedItem so you can read the Binding you created in your cells. And Replace your VStack with your new ItemCell:
struct YourParentView: View {
#State var selectedItem: Item? = nil
var body: some View {
ScrollView(.horizontal, showsIndicators: false){
LazyHStack{
ForEach(self.staffs.indices, id: \.self){ staff in
ItemCell(item: staff, selectedItem: self.$selectedItem)
}
}
}
}
}
Now when you click on the item, a border should appear. You may need to play with the border depending on your design, and the clipShape you have used of Circle(). Good Luck comrade.