#State var doesn't update when changing the value outside the view - xcode

I need to declare the checkBox array using "#State" if I want to use it inside the view using $checkBox and it works fine but when I want to update the toggles (1 or more elements of the array) in a function outside the view, the array is not updated. I tried to declare it using #Binding and #Published but without success. I saw many similar Q&A but I didn't find a solution for my case. This is my code:
struct CheckboxStyle: ToggleStyle {
func makeBody(configuration: Self.Configuration) -> some View {
return HStack {
Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(configuration.isOn ? .green : .gray)
.onTapGesture { configuration.isOn.toggle() }
configuration.label
}
}
}
struct ContentView: View {
#State var checkBox = Array(repeating: false, count: 14)
let checkBoxName: [LocalizedStringKey] = ["checkPressure", "checkVisibility", "checkCloudCover", "checkAirTemp", "checkWaterTemp", "checkWindDir", "checkWindSpeed", "checkWindGust", "checkCurrentDir", "checkCurrentSpeed", "checkSwellDir", "checkWaveHeight", "checkWavePeriod", "checkTideHeight"]
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Group {
ForEach(0..<7) { t in
Toggle(isOn: $checkBox[t], label: {
Text(checkBoxName[t]).font(.footnote).fontWeight(.light)
}).toggleStyle(CheckboxStyle()).padding(5)
}
}
Group {
ForEach(7..<14) { t in
Toggle(isOn: $checkBox[t], label: {
Text(checkBoxName[t]).font(.footnote).fontWeight(.light)
}).toggleStyle(CheckboxStyle()).padding(5)
}
}
}
}
}
}
Thx for help

Related

SwiftUI background image moves when keyboard shows

I have an image background, which should stay in place when the keyboard shows, but instead it moves up together with everything on the screen. I saw someone recommend using ignoresSafeArea(.keyboard), and this question Simple SwiftUI Background Image keeps moving when keyboard appears, but neither works for me. Here is my super simplified code sample. Please keep in mind that while the background should remain unchanged, the content itself should still avoid the keyboard as usual.
struct ProfileAbout: View {
#State var text: String = ""
var body: some View {
VStack {
TextField("write something", text: $text)
Spacer()
Button("SomeButton") {}
}
.background(
Image("BackgroundName")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea(.keyboard)
)
}
}
Here a possible salvation:
import SwiftUI
struct ContentView: View {
#Environment(\.verticalSizeClass) var verticalSizeClass
#State var valueOfTextField: String = String()
var body: some View {
GeometryReader { proxy in
Image("Your Image name here").resizable().scaledToFill().ignoresSafeArea()
ZStack {
if verticalSizeClass == UserInterfaceSizeClass.regular { TextFieldSomeView.ignoresSafeArea(.keyboard) }
else { TextFieldSomeView }
VStack {
Spacer()
Button(action: { print("OK!") }, label: { Text("OK").padding(.horizontal, 80.0).padding(.vertical, 5.0).background(Color.yellow).cornerRadius(5.0) }).padding()
}
}
.position(x: proxy.size.width/2, y: proxy.size.height/2)
}
}
var TextFieldSomeView: some View {
return VStack {
Spacer()
TextField("write something", text: $valueOfTextField).padding(5.0).background(Color.yellow).cornerRadius(5.0).padding()
Spacer()
}
}
}
u can use GeometryReader
get parent View size
import SwiftUI
import Combine
struct KeyboardAdaptive: ViewModifier {
#State private var keyboardHeight: CGFloat = 0
func body(content: Content) -> some View {
GeometryReader { geometry in
content
.padding(.bottom, keyboardHeight)
.onReceive(Publishers.keyboardHeight) {
self.keyboardHeight = $0
}
}
}
}
extension Publishers {
static var keyboardHeight: AnyPublisher<CGFloat, Never> {
let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
.map { $0.keyboardHeight }
let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
.map { _ in CGFloat(0) }
return MergeMany(willShow, willHide)
.eraseToAnyPublisher()
}
}
extension View {
func keyboardAdaptive() -> some View {
ModifiedContent(content: self, modifier: KeyboardAdaptive())
}
}
struct ProfileAbout: View {
#State var text: String = ""
var body: some View {
VStack {
TextField("write something", text: $text)
Spacer()
Button("SomeButton") {}
}
.background(
Image("BackgroundName")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea(.keyboard)
)
.keyboardAdaptive()
}
}

How do I switch between screens in TabView and from the latter to the other View?

I created a simple collection with a button jump to the next View. From the last View there should be a transition to AddItemView, but it doesn't happen - it goes back to the first screen.
Can you tell me where I made a mistake?
What is the correct way to place the background Image on the first collection screen, so that it won't be on the following screens?
import SwiftUI
struct AddItemView: View {
var body: some View {
Text("Hallo!")
}
}
struct ContentView: View {
var colors: [Color] = [ .orange, .green, .yellow, .pink, .purple ]
var emojis: [String] = [ "👻", "🐱", "🦊" , "👺", "🎃"]
#State private var tabSelection = 0
var body: some View {
TabView(selection: $tabSelection) {
ForEach(0..<emojis.endIndex) { index in
VStack {
Text(emojis[index])
.font(.system(size: 150))
.frame(minWidth: 30, maxWidth: .infinity, minHeight: 0, maxHeight: 250)
.background(colors[index])
.clipShape(RoundedRectangle(cornerRadius: 30))
.padding()
.tabItem {
Text(emojis[index])
}
Button(action: {
self.tabSelection += 1
}) {
if tabSelection == emojis.endIndex {
NavigationLink(destination: AddItemView()) {
Text("Open View")
}
} else {
Text("Change to next tab")
}
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.tabViewStyle(PageTabViewStyle.init(indexDisplayMode: .never))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In this code, you have not to use NavigationView. It's required to navigate to the next screen. Similar concept like Push view controller if navigation controller exists. Also, remove endIndex and use indices.
struct ContentView: View {
var colors: [Color] = [ .orange, .green, .yellow, .pink, .purple ]
var emojis: [String] = [ "👻", "🐱", "🦊" , "👺", "🎃"]
#State private var tabSelection = 0
var body: some View {
NavigationView { //<- add navigation view
TabView(selection: $tabSelection) {
ForEach(emojis.indices) { index in //<-- use indices
VStack {
Text(emojis[index])
.font(.system(size: 150))
.frame(minWidth: 30, maxWidth: .infinity, minHeight: 0, maxHeight: 250)
.background(colors[index])
.clipShape(RoundedRectangle(cornerRadius: 30))
.padding()
.tabItem {
Text(emojis[index])
}
Button(action: {
self.tabSelection += 1
}) {
if tabSelection == emojis.count - 1 { //<- use count
NavigationLink(destination: AddItemView()) {
Text("Open View")
}
} else {
Text("Change to next tab")
}
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.tabViewStyle(PageTabViewStyle.init(indexDisplayMode: .never))
}
}
}
If you have already a navigation link from the previous screen then, the problem is you are using endIndex in the wrong way. Check this thread for correct use (https://stackoverflow.com/a/36683863/14733292).

SwiftUI matchedGeometry + LazyVStack = crash

It took me hours to construct this example, and I'm not sure if I am doing something wrong or there is a bug crashing the app when using matchedGeometry + LazyVStack.
In the video below the app crashed when I click on third rectangle (which was not visible when the app started). Crash disappears if I replace LazyVStack with VStack, but obviously I want to lazy load my things.
Xcode version: Version 12.0.1 (12A7300)
struct ContentView: View {
#Namespace var namespace
#State var selected: Int?
var body: some View {
ZStack {
VStack {
Text("Cool rectangles")
if selected == nil {
ScrollView(.vertical, showsIndicators: false) {
BoxList(namespace: namespace, selected: $selected)
}
}
}
if let id = selected {
Rectangle()
.foregroundColor(.red)
.matchedGeometryEffect(id: id, in: namespace)
.onTapGesture {
withAnimation{
selected = nil
}
}
}
}
}
}
struct BoxList: View {
let namespace: Namespace.ID
#Binding var selected: Int?
var body: some View {
LazyVStack {
ForEach(0..<10){ item in
Rectangle()
.matchedGeometryEffect(id: item, in: namespace)
.frame(width: 200, height: 200)
.onTapGesture {
withAnimation {
selected = item
}
}
}
}
}
}
The problem is that you destroy ScrollView breaking matched layout.
Here is fixed variant. Tested with Xcode 12 / iOS 14
struct ContentView: View {
#Namespace var namespace
#State var selected: Int?
var body: some View {
ZStack {
VStack {
Text("Cool rectangles")
ScrollView(.vertical, showsIndicators: false) {
BoxList(namespace: namespace, selected: $selected)
}.opacity(selected == nil ? 1 : 0)
} // << or place here opacity modifier here
if let id = selected {
Rectangle()
.foregroundColor(.red)
.matchedGeometryEffect(id: id, in: namespace)
.onTapGesture {
withAnimation{
selected = nil
}
}
}
}
}
}
struct BoxList: View {
let namespace: Namespace.ID
#Binding var selected: Int?
var body: some View {
LazyVStack {
ForEach(0..<10){ item in
if item == selected {
Color.clear // placeholder to avoid duplicate match id run-time warning
.frame(width: 200, height: 200)
} else {
Rectangle()
.matchedGeometryEffect(id: item, in: namespace)
.frame(width: 200, height: 200)
.onTapGesture {
withAnimation {
selected = item
}
}
}
}
}
}
}

SwiftUI URLImage Horizontal ScrollView

So I've been going through a SwiftUI instagram tutorial and learnt how to load images uploaded by user to firebase in the standard 3x3 instagram view but am now wanting to expand my knowledge and practice doing it in horizontal scrollview.
Here's what I have to create grid view:
import SwiftUI
import URLImage
import FirebaseAuth
struct Photo: Identifiable {
let id = UUID()
var photo = ""
}
struct PhotoView: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#EnvironmentObject var session: SessionStore
#ObservedObject var profileViewModel = ProfileViewModel()
var body: some View {
return
ScrollView {
if !profileViewModel.isLoading {
VStack(alignment: .leading, spacing: 1) {
// rows
ForEach(0..<self.profileViewModel.splitted.count) { index in
HStack(spacing: 1) {
// Columns
ForEach(self.profileViewModel.splitted[index], id: \.postId) { post in
URLImage(URL(string: post.mediaUrl)!,
content: {
$0.image
.resizable()
.aspectRatio(contentMode: .fill)
}).frame(width: UIScreen.main.bounds.width / 3, height: UIScreen.main.bounds.width / 3).clipped().cornerRadius(5)
}
}
}
}.frame(width: UIScreen.main.bounds.width, alignment: .leading).padding(.top, 2)
}
}.navigationBarTitle(Text("Photos"), displayMode: .inline).navigationBarBackButtonHidden(true).navigationBarItems(leading: Button(action : {
self.mode.wrappedValue.dismiss()
}) {
Image(systemName: "arrow.left")
}).onAppear {
self.profileViewModel.loadUserPosts(userId: Auth.auth().currentUser!.uid)
}
}
}
extension Array {
func splitted(into size:Int) -> [[Element]] {
var splittedArray = [[Element]]()
if self.count >= size {
for index in 0...self.count {
if index % size == 0 && index != 0 {
splittedArray.append(Array(self[(index - size)..<index]))
} else if (index == self.count) {
splittedArray.append(Array(self[index - 1..<index]))
}
}
} else {
splittedArray.append(Array(self[0..<self.count]))
}
return splittedArray
}
}
class ProfileViewModel: ObservableObject {
#Published var posts: [Post] = []
#Published var isLoading = false
var splitted: [[Post]] = []
func loadUserPosts(userId: String) {
isLoading = true
Api.User.loadPosts(userId: userId) { (posts) in
self.isLoading = false
self.posts = posts
self.splitted = self.posts.splitted(into: 3)
}
}
}
And this is what it looks like:
This is the sample code for what I am trying to achieve:
import SwiftUI
import URLImage
import FirebaseAuth
struct TestView: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
var body: some View {
VStack {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 2) {
ForEach(1..<5) { _ in
Image("photo3").resizable()
.clipShape(Rectangle())
.aspectRatio(contentMode: ContentMode.fill)
.frame(width: 100, height: 100).cornerRadius(10).opacity(1).shadow(radius: 4)
}
}
}.navigationBarTitle(Text("Photos"), displayMode: .inline).navigationBarBackButtonHidden(true).navigationBarItems(leading: Button(action : {
self.mode.wrappedValue.dismiss()
}) {
Image(systemName: "arrow.left")
})
Spacer()
}.padding()
}
}
and here is the sample image of what I want it to look like:
I'm really struggling to understand the ForLoop part and how I can retrieve the image to just be in a simple scrollView.
Any help would be much appreciated!
Thanks!
You want to loop over the posts in your model. Borrowing from your earlier code, you need something like this:
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 2) {
ForEach(self.profileViewModel.posts, id: \.postId) { post in
URLImage(URL(string: post.mediaUrl)!,
content: {
$0.image
.resizable()
.aspectRatio(contentMode: .fill)
}
)
.frame(width: 100, height: 100)
.clipped()
.cornerRadius(10)
.shadow(radius: 4)
}
}
}
vacawama has already posted the perfect solution to make it look like your example.
Just to add why you achieve the result, you are getting.
The difference between your code and the sample code is that you are using two ForEach, one for the rows and one for the columns. The array gets splitted with your extension, so you get rows and columns.
//Rows
ForEach(0..<self.profileViewModel.splitted.count) { index in
HStack(spacing: 1) {
// Columns
ForEach(self.profileViewModel.splitted[index], id: \.postId) { post in
Your comments already stating how it works. If you want to have all your images in a horizontal scroller, you just need one ForEach which outputs all your images in a ScrollView.
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 2) {
ForEach(self.profileViewModel.posts, id: \.postId) { post in

Has anyone updated SwiftUI Popovers to work in Xcode beta-3?

Updated to Xcode beta-3, Popover was deprecated... having one hell of a time trying to figure out how to make it work again!?!?
It no longer "pops up" it slides up from the bottom.
It's no longer positioned or sized correctly, takes up the whole screen.
Once dismissed, it never wants to appear again.
This was the old code, that worked perfectly...
struct ExerciseFilterBar : View {
#Binding var filter: Exercise.Filter
#State private var showPositions = false
var body: some View {
HStack {
Spacer()
Button(action: { self.showPositions = true } ) {
Text("Position")
}
.presentation(showPositions ? Popover(content: MultiPicker(items: Exercise.Position.allCases, selected:$filter.positions),
dismissHandler: { self.showPositions = false })
: nil)
}
.padding()
}
}
And this is the new code...
struct ExerciseFilterBar : View {
#Binding var filter: Exercise.Filter
#State private var showPositions = false
var body: some View {
HStack {
Spacer()
Button(action: { self.showPositions = true } ) {
Text("Position")
}
.popover(isPresented: $showPositions) {
MultiPicker(items: Exercise.Position.allCases, selected:self.$filter.positions)
.onDisappear { self.showPositions = false }
}
}
.padding()
}
}
I ended up using PresentationLink just so I can move forward with everything else...
struct ExerciseFilterBar : View {
#Binding var filter: Exercise.Filter
var body: some View {
HStack {
Spacer()
PresentationLink(destination: MultiPicker(items: Exercise.Position.allCases, selected:$filter.positions)) {
Text("Position")
}
}
.padding()
}
}
It works, as far as testing is concerned, but it's not a popover.
Thanks for any suggestions!
BTW, this code is being in the iPad simulator.
On OSX the code below works fine
struct ContentView : View {
#State var poSelAbove = false
#State var poSelBelow = false
#State var pick : Int = 1
var body: some View {
let picker = Picker(selection: $pick, label: Text("Pick option"), content:
{
Text("Option 0").tag(0)
Text("Option 1").tag(1)
Text("Option 2").tag(2)
})
let popoverWithButtons =
VStack {
Button("Not Dismiss") {
}
Divider()
Button("Dismiss") {
self.poSelAbove = false
}
}
.padding()
return VStack {
Group {
Button("Show button popover above") {
self.poSelAbove = true
}.popover(isPresented: $poSelAbove, arrowEdge: .bottom) {
popoverWithButtons
}
Divider()
Button("Show picker popover below") {
self.poSelBelow = true
}.popover(isPresented: $poSelBelow, arrowEdge: .top) {
Group {
picker
}
}
}
Divider()
picker
.frame(width: 300, alignment: .center)
Text("Picked option: \(self.pick)")
.font(.subheadline)
}
// comment the line below for iOS
.frame(width: 800, height: 600)
}
On iOS (iPad) the popover will appear in a strange transparent full screen mode. I don't think this is intended. I have added the problem to my existing bug report.

Resources