Place map above another view Swift UI - swiftui

I want the map to be above the bottom-slider always Whenever the bottom-slider move up the map go with it and adjust always stay above it.
This is my code, Btw I started a swift today
var body: some View {
ZStack(alignment: Alignment.top) {
MapView()
// From here I want the MapView which is google maps above the SlideOverCard
SlideOverCard {
VStack {
Text("Whatever")
.font(.headline)
}.frame(width: 1000)
}
}
.edgesIgnoringSafeArea(.vertical)
}

You can give either .zIndex or change the order of views in ZStack(the latest will be the top-most)
A)
ZStack(alignment: Alignment.top) {
MapView().zIndex(1) // by default all views has 0
SlideOverCard {
B)
ZStack(alignment: Alignment.top) {
SlideOverCard {
// ... other code here
}
MapView() // will be above
}

Related

How to keep a view always centered independently from the size of the left view

I have a simple HStack containing a few views and I would like to keep one of the views ("Center") always centered. The size of the left view can change dynamically.
struct ExperimentView: View {
var body: some View {
HStack() {
Text("A")
.background(.red)
Spacer()
Text("Center")
.background(.yellow)
Spacer()
Button("B1") {
}
Button("B2") {
}
}
}
}
The Center view is not centered and moves to the right depending on the size of Text. I have tried something using alignmentGuide but I had no success.
A possible way is to wrap the "Center" text and the other views in a ZStack
struct ExperimentView: View {
var body: some View {
ZStack {
Text("Center")
.background(.yellow)
HStack {
Text("A")
.background(.red)
Spacer()
Button("B1") { }
Button("B2") { }
}
}
}
}
But this will not prevent the objects from overlapping if A becomes too wide.

Is there a way to conditionally flip the order of an HStack's contents?

I'm using an HStack to layout some elements in my view hierarchy. I'd love to be able to conditionally flip the order of the elements.
HStack {
Text("Hello")
Text("World")
}
The idea is that this will either be layouted as "Hello World" or "World Hello" depending on my view's state.
HStack itself doesn't provide any functionality for this, but it also appears to be pretty much impossible trying to pull this out of the view itself attempting to use other ViewBuilders, ForEach-based approaches, etc.
The only way I can resolve this is by actually specifying both layouts entirely, which is what I'm trying to avoid.
let isFlipped: Bool
HStack {
if isFlipped {
Text("World")
Text("Hello")
} else {
Text("Hello")
Text("World")
}
}
Here is possible generic approach for any pair of views in any container base on using ViewBuilder.
Tested with Xcode 12
struct TestHStackFlip: View {
#State private var flipped = false
var body: some View {
VStack {
HStack {
FlipGroup(if: flipped) {
Text("Text1")
Text("Text2")
}
}.animation(.default) // animatable
Divider()
Button("Flip") { self.flipped.toggle() }
}
}
}
#ViewBuilder
func FlipGroup<V1: View, V2: View>(if value: Bool,
#ViewBuilder _ content: #escaping () -> TupleView<(V1, V2)>) -> some View {
let pair = content()
if value {
TupleView((pair.value.1, pair.value.0))
} else {
TupleView((pair.value.0, pair.value.1))
}
}

Runtime error: precondition failure: attribute failed to set an initial value

I have a view BugSplitView which works fine alone but causes a
precondition failure: attribute failed to set an initial value
error when navigated to in either preview or the simulator.
The view has an upper part (Color) and a lower part (Color) separated by a horizontal button bar and laid out using the GeometeryReader and a split state. When it is the destination of NavigationButton it doesn't show properly in the Preview and reports the assertion above when run in the simulator. Remove the BugButtonBar and it works. Got me stumped! Help.
import SwiftUI
struct BugSplitView: View {
#State var split : CGFloat = 0.75
var buttons : [BugButtonBar.Info]{
[BugButtonBar.Info(title: "title", imageName: "text.insert"){}]
}
var body: some View {
GeometryReader{ g in
VStack(spacing: 0){
Color.gray
.frame(width: g.size.width, height: (g.size.height) * self.split)
VStack{
BugButtonBar(infos: self.buttons)
Color(white: 0.3)
}
.frame(height: (g.size.height) * (1 - self.split))
}
}.edgesIgnoringSafeArea(.all)
}
}
struct BugButtonBar : View{
struct Info : Identifiable {
var id = UUID()
var title : String
var imageName : String
var action: () -> Void
}
var infos : [Info]
func color() -> Color{
Color.black
}
var body: some View {
HStack(){
Spacer()
ForEach(self.infos){ info in
Button(action: info.action){
Text(info.title)
}
Spacer()
}
}
}
}
struct ShowBugView : View{
var body : some View{
NavigationView {
NavigationLink(destination: BugSplitView()){
Text("Show Bug")
}
}
}
}
struct BugSplitView_Previews: PreviewProvider {
static var previews: some View {
Group{
BugSplitView()
ShowBugView()
}
}
}
The problem is that your buttons are declared as computed property. To solve the crash declare them like this:
var buttons = [BugButtonBar.Info(title: "title", imageName: "text.insert"){}]
Turns out the id property of struct Info was the problem. Changed it to a computed property as follows:
var id : String {
title + imageName
}
Great example of why I love/hate SwiftUI.
For me it was displayMode inline in navigation bar title. Removing it fixes this problem.
Crash
.navigationBarTitle("Title", displayMode: .inline)
No crash
.navigationBarTitle("Title")
Since it seems that this error - which can't be directly debugged - can be caused by so many different issues, I figured I'd throw my case up here too.
In my case, the error I was getting was:
precondition failure: attribute failed to set an initial value - 128
The issue was that I was attempting to present a sheet on a VStack that contained a NavigationView inside of it, like the below:
var body: some View {
VStack(alignment: .center) {
if /* some condition */ {
/* some other content */
} else {
NavigationView {
/* view content */
}
}
}.sheet(isPresented: /* Binding value */) {
/* sheet content */
}
}
The fix was to make sure that the sheet was being presented on the NavigationView instead:
var body: some View {
NavigationView {
VStack(alignment: .center) {
if /* some condition */ {
/* some other content */
} else {
/* view content */
}
}
}.sheet(isPresented: /* Binding value */) {
/* sheet content */
}
}
Seems obvious in hindsight, but it would have been nice to get a bit more information when the crash occurred in the first place.
I had this error. In my case, it was caused by having a NavigationView inside both blocks of an if-else statement.
// bad
if someBool {
NavigationView {
Text("some content")
}
} else {
NavigationView {
Text("different content")
}
}
// good
NavigationView {
if someBool {
Text("some content")
} else {
Text("different content")
}
}
In my case it was setting a value to a binding property when view disappears, a property that changes a view like this:
.onDisappear(perform: {
withAnimation(.easeInOut) {
self.action.collageWidthSize = 2.0 /* modifies next view border */
}
})
Setting this in the next view's onAppear fixed it:
.onAppear {
withAnimation(.easeInOut) {
self.action.collageWidthSize = 2.0
}
}
Ok, I was bit by this. Xcode 11.6.
My views are probably a bit convoluted, but what i'm doing is if a user puts the view into an 'edit' mode all of the cells change their presentation. I was getting this error seemingly at random when i switched back. I fixed it (fingers still crossed) by removing an unnecessary binding. I was passing a boolean binding into some of the subviews so that they know what state things are in, and how to be presented. Thing is, they don't need to respond to the boolean change, because the parent is being redrawn anyway, and it can just recreate the children. They don't need to be notified that they should change state, they are simply destroyed and recreated.
I used NavigationView as the root of TabView:
NavigationView {
TabView {
}
}
Then in the TabView I used NavigationView too, so due to this error. The solution is only use one NavigationView.

Get onAppear behaviour from list in ScrollView in SwiftUI

When creating a List view onAppear triggers for elements in that list the way you would expect: As soon as you scroll to that element the onAppear triggers. However, I'm trying to implement a horizontal list like this
ScrollView(.horizontal) {
HStack(spacing: mySpacing) {
ForEach(items) { item in
MyView(item: item)
.onAppear { \\do something }
}
}
}
Using this method the onAppear triggers for all items at once, that is to say: immediately, but I want the same behavior as for a List view. How would I go about doing this? Is there a manual way to trigger onAppear, or control when views load?
Why I want to achieve this: I have made a custom Image view that loads an image from an URL only when it appears (and substitutes a placeholder in the mean time), this works fine for a List view, but I'd like it to also work for my horizontal 'list'.
As per SwiftUI 2.0 (XCode 12 beta 1) this is finally natively solved:
In a LazyHStack (or any other grid or stack with the Lazy prefix) elements will only initialise (and therefore trigger onAppear) when they appear on screen.
Here is possible approach how to do this (tested/worked with Xcode 11.2 / iOS 13.2)
Demo: (just show dynamically first & last visible cell in scrollview)
A couple of important View extensions
extension View {
func rectReader(_ binding: Binding<CGRect>, in space: CoordinateSpace) -> some View {
self.background(GeometryReader { (geometry) -> AnyView in
let rect = geometry.frame(in: space)
DispatchQueue.main.async {
binding.wrappedValue = rect
}
return AnyView(Rectangle().fill(Color.clear))
})
}
}
extension View {
func ifVisible(in rect: CGRect, in space: CoordinateSpace, execute: #escaping (CGRect) -> Void) -> some View {
self.background(GeometryReader { (geometry) -> AnyView in
let frame = geometry.frame(in: space)
if frame.intersects(rect) {
execute(frame)
}
return AnyView(Rectangle().fill(Color.clear))
})
}
}
And a demo view of how to use them with cell views being in scroll view
struct TestScrollViewOnVisible: View {
#State private var firstVisible: Int = 0
#State private var lastVisible: Int = 0
#State private var visibleRect: CGRect = .zero
var body: some View {
VStack {
HStack {
Text("<< \(firstVisible)")
Spacer()
Text("\(lastVisible) >> ")
}
Divider()
band()
}
}
func band() -> some View {
ScrollView(.horizontal) {
HStack(spacing: 10) {
ForEach(0..<50) { i in
self.cell(for: i)
.ifVisible(in: self.visibleRect, in: .named("my")) { rect in
print(">> become visible [\(i)]")
// do anything needed with visible rects, below is simple example
// (w/o taking into account spacing)
if rect.minX <= self.visibleRect.minX && self.firstVisible != i {
DispatchQueue.main.async {
self.firstVisible = i
}
} else
if rect.maxX >= self.visibleRect.maxX && self.lastVisible != i {
DispatchQueue.main.async {
self.lastVisible = i
}
}
}
}
}
}
.coordinateSpace(name: "my")
.rectReader(self.$visibleRect, in: .named("my"))
}
func cell(for idx: Int) -> some View {
RoundedRectangle(cornerRadius: 10)
.fill(Color.yellow)
.frame(width: 80, height: 60)
.overlay(Text("\(idx)"))
}
}
I believe what you want to achieve can be done with LazyHStack.
ScrollView {
LazyVStack {
ForEach(1...100, id: \.self) { value in
Text("Row \(value)")
.onAppear {
// Write your code for onAppear here.
}
}
}
}

Why does binding to the Picker not work anymore in swiftui?

When I run a Picker Code in the Simulator or the Canvas, the Picker goes always back to the first option with an animation or just freezes. This happens since last Thursday/Friday. So I checked some old simple code, where it worked before that and it doesn't work for me there, too.
This is the simple old Code. It doesn't work anymore in beta 3, 4 and 5.
struct PickerView : View {
#State var selectedOptionIndex = 0
var body: some View {
VStack {
Text("Option: \(selectedOptionIndex)")
Picker(selection: $selectedOptionIndex, label: Text("")) {
Text("Option 1")
Text("Option 2")
Text("Option 3")
}
}
}
}
In my newer code, I used #ObservedObject, but also here it doesn't work.
Also I don't get any errors and it builds and runs.
Thank you for any pointers.
----EDIT----- Please look at the answer first
After the help, that I could use the .tag() behind all Text()like Text("Option 1").tag(), it now takes the initial value and updates it inside the view. If I use #ObservedObject like here:
struct PickerView: View {
#ObservedObject var data: Model
let width: CGFloat
let height: CGFloat
var body: some View {
VStack(alignment: .leading) {
Picker(selection: $data.exercise, label: Text("select exercise")) {
ForEach(data.exercises, id: \.self) { exercise in
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise))
}
}
.frame(width: width, height: (height/2), alignment: .center)
}
}
}
}
Unfortunately it doesn't reflect changes on the value, if I make these changes in another view, one navigationlink further. And also it doesn't seem to work with the my code above, where I use firstIndex(of: exercise)
---EDIT---
Now the code above works if I change
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise))
into
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise)!)
because it couldn't work with an optional.
The answer summarized:
With the .tag() behind the Options it works. It would look like following:
Picker(selection: $selectedOptionIndex, label: Text("")) {
ForEach(1...3) { index in
Text("Option \(index)").tag(index)
}
}
If you use a range of Objects it could look like this:
Picker(selection: $data.exercises, label: Text("")) {
ForEach(0..<data.exercises.count) { index in
Text("\(data.exercises[index])").tag(index)
}
}
I am not sure if it is intended, that .tag() is needed to be used here, but it's at least a workaround.
I found a way to simplify the code a bit without the need of operating on indicies and tags.
At first, make sure to conform your model to Identifiable protocol like this (this is actually a key part, as it enables SwiftUI to differentiate elements):
public enum EditScheduleMode: String, CaseIterable, Identifiable {
case closeSchedule
case openSchedule
public var id: EditScheduleMode { self }
var localizedTitle: String { ... }
}
Then you can declare viewModel like this:
public class EditScheduleViewModel: ObservableObject {
#Published public var editScheduleMode = EditScheduleMode.closeSchedule
public let modes = EditScheduleMode.allCases
}
and UI:
struct ModeSelectionView: View {
private let elements: [EditScheduleMode]
#Binding private var selectedElement: EditScheduleMode
internal init?(elements: [EditScheduleMode],
selectedElement: Binding<EditScheduleMode>) {
self.elements = elements
_selectedElement = selectedElement
}
internal var body: some View {
VStack {
Picker("", selection: $selectedElement) {
ForEach(elements) { element in
Text(element.localizedTitle)
}
}
.pickerStyle(.segmented)
}
}
}
With all of those you can create a view like this:
ModeSelectionView(elements: viewModel.modes, selectedElement: $viewModel.editScheduleMode)