SwiftUI How to use .refreshable view modifier without a list? - ios

iOS 15 introduces the '.refreshable' View Modifier, but most of the examples I've seen use a List. I want to implement a pull to refresh feature in an app that does not use a list but just a VStack with Text views like the below. How can I implement this so that pull down to refresh will refresh my data?
import SwiftUI
struct ContentView: View {
#State var songName = "Song"
#State var artistName = "Artist"
var body: some View {
VStack {
Text(songName)
Text(artistName)
}
.refreshable {
reloadData()
}
}
private func reloadData() {
songName = "Let it Be"
artistName = "The Beatles"
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

cannot find $exercisePlan in scope (SwiftUI) [duplicate]

How do I generate a preview provider for a view which has a binding property?
struct AddContainer: View {
#Binding var isShowingAddContainer: Bool
var body: some View {
Button(action: {
self.isShowingAddContainer = false
}) {
Text("Pop")
}
}
}
struct AddContainer_Previews: PreviewProvider {
static var previews: some View {
// ERROR HERE <<<-----
AddContainer(isShowingAddContainer: Binding<Bool>()
}
}
In Code above, How to pass a Binding<Bool> property in an initialiser of a view?
Just create a local static var, mark it as #State and pass it as a Binding $
struct AddContainer_Previews: PreviewProvider {
#State static var isShowing = false
static var previews: some View {
AddContainer(isShowingAddContainer: $isShowing)
}
}
If you want to watch the binding:
Both other solutions [the "static var" variant AND the "constant(.false)"-variant work for just seeing a preview that is static. But you cannot not see/watch the changes of the value intended by the button action, because you get only a static preview with this solutions.
If you want to really watch (in the life preview) this changing, you could easily implement a nested view within the PreviewProvider, to - let's say - simulate the binding over two places (in one preview).
import SwiftUI
struct BoolButtonView: View {
#Binding var boolValue : Bool
var body: some View {
VStack {
Text("The boolValue in BoolButtonView = \(boolValue.string)")
.multilineTextAlignment(.center)
.padding()
Button("Toggle me") {
boolValue.toggle()
}
.padding()
}
}
}
struct BoolButtonView_Previews: PreviewProvider {
// we show the simulated view, not the BoolButtonView itself
static var previews: some View {
OtherView()
.preferredColorScheme(.dark)
}
// nested OTHER VIEW providing the one value for binding makes the trick
struct OtherView : View {
#State var providedValue : Bool = false
var body: some View {
BoolButtonView(boolValue: $providedValue)
}
}
}
Other way
struct AddContainer_Previews: PreviewProvider {
static var previews: some View {
AddContainer(isShowingAddContainer: .constant(false))
}
}

Swiftui List animation

I am trying to animate the contents of a list of points. The list is sorted from highest to lowest, so it reorganizes as lower values are higher than the row above. The animation works great by using .animation(.default) on the list, however, it also animates the entire list when I open the view. The whole list floats into place. I would the list to be static, and just the rows move when necessary to reorder
List {
ForEach(players) { player in
Text(player.score)
}
}.animation(.default)
To animate only when something in the State data changes, use a withAnimation surrounding the part where you change it rather than the whole list:
import SwiftUI
struct Player: Identifiable {
var id = UUID()
var score: String
}
struct ContentView: View {
#State var players: [Player] = [
.init(score: "2"),
.init(score: "3"),
.init(score: "6"),
.init(score: "1")]
var body: some View {
VStack {
Button("shuffle") {
withAnimation(.easeIn) {
players.shuffle()
}
}
List {
ForEach(players) { player in
Text(player.score)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

How to pass an Object to new SwiftUI view

I'm having trouble getting started in SwiftUI. What I want to do is rather simple at least I thought it would. What I want to do is that the ContentView expects a first and last name of a person. The button "Add to list" adds the person to a list and the second button shows a list of all added persons in a second view. I read about the property wrappers but I cannot get it to work. Do I need to change struct Person to a class in order to use the #ObservedObject for initialising #StateObject var listOfPersons = [Person]() or is there a more simpler Swift like way to pass the list to my PersonList View?
My project code:
ContentView.swift
struct ContentView: View {
#State var firstName: String = ""
#State var lastName: String = ""
#StateObject var listOfPersons = [Person]()
var body: some View {
NavigationView {
ZStack (alignment: .top){
Color(.orange).opacity(0.2).edgesIgnoringSafeArea(.all)
VStack {
Text("Hello stranger")
.font(.title)
TextField("Fist name:", text: $firstName)
.padding()
TextField("Last name:", text: $firstName)
.padding()
HStack(spacing: 40) {
Button("Add to list") {
listOfPersons.append(Person(firstName: firstName, lastName: lastName))
}
.padding()
NavigationLink(destination: PersonList()) {
Text("Show list")
}
...
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
PersonList.swift
import SwiftUI
struct PersonList: View {
#Binding var listOfPersons = [Person]()
var body: some View {
Color(.orange).opacity(0.2).edgesIgnoringSafeArea(.all)
List(listOfPersons) { person in
PersonRow(person: person) }
}
}
struct PersonList_Previews: PreviewProvider {
static var previews: some View {
PersonList()
}
}
Person.swift
import Foundation
struct Person {
var firstName: String
var lastName: String
}
PersonRow.swift
import SwiftUI
struct PersonRow: View {
var person: Person
var body: some View {
Text("\(person.firstName), \(person.lastName)")
}
}
Your code has a couple problems, but you're on the right track. First, replace #StateObject var listOfPersons = [Person]() with:
#State var listOfPersons = [Person]()
#StateObject is for an instance of a ObservableObject class. #State is what you should be using for a simple array of the Person struct.
Then, in your current code, you're just instantiating a plain PersonList without any parameters. You want to pass in listOfPersons.
/// here!
NavigationLink(destination: PersonList(listOfPersons: $listOfPersons)) {
Text("Show list")
}
The $ sign gets the underlying Binding<[Person]> from listOfPersons, which means that any changes made inside PersonList's listOfPersons will be reflected back to ContentView's listOfPersons.
Finally, in PersonList, change #Binding var listOfPersons = [Person]() to
struct PersonList: View {
#Binding var listOfPersons: [Person]
...
}
#Binding almost never needs to have a default value, since it's always passed in.

SwiftUI Using ObservableObject in View on New Sheet

I am struggling with figuring out how to use a value assigned to a variable in an ObservableObject class in another view on another sheet. I see that it gets updated, but when I access it in the new view on the new sheet it is reset to the initialized value. How do I get it to retain the new value so I can use it in a new view on a new sheet?
ContentData.swift
import SwiftUI
import Combine
class ContentData: ObservableObject {
#Published var text: String = "Yes"
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#ObservedObject var contentData = ContentData()
#State private var inputText: String = ""
#State private var showNewView: Bool = false
var body: some View {
VStack {
TextField("Text", text: $inputText, onCommit: {
self.assignText()
})
Button(action: {
self.showNewView = true
}) {
Text("Go To New View")
}
.sheet(isPresented: $showNewView) {
NewView(contentData: ContentData())
}
}
}
func assignText() {
print(contentData.text)
contentData.text = inputText
print(contentData.text)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(contentData: ContentData())
}
}
NewView.swift
import SwiftUI
struct NewView: View {
#ObservedObject var contentData = ContentData()
var body: some View {
VStack {
Text(contentData.text)
}
}
}
struct NewView_Previews: PreviewProvider {
static var previews: some View {
NewView(contentData: ContentData())
}
}
I have tried many, many different methods I have seen from other examples. I tried doing it with #EnviromentObject but could not get that to work either. I also tried a different version of the NewView.swift where I initialized the value with:
init(contentData: ContentData) {
self.contentData = contentData
self._newText = State<String>(initialValue: contentData.text)
}
I think I am close, but I do not see what I am missing. Any help would be very much appreciated. Thanks.
#ObservedObject var contentData = ContentData()
ContentData() in the above line creates a new instance of the class ContentData.
You should pass the same instance from ContentView to NewView to retain the values. Like,
.sheet(isPresented: $showNewView) {
NewView(contentData: self.contentData)
}
Stop creating new instance of ContentData in NewView and add the ability to inject ContentData from outside,
struct NewView: View {
#ObservedObject var contentData: ContentData
...
}

How do I update a text label in SwiftUI?

I have a SwiftUI text label and i want to write something in it after I press a button.
Here is my code:
Button(action: {
registerRequest() // here is where the variable message changes its value
}) {
Text("SignUp")
}
Text(message) // this is the label that I want to change
How do I do this?
With only the code you shared it is hard to say exactly how you should do it but here are a couple good ways:
The first way is to put the string in a #State variable so that it can be mutated and any change to it will cause an update to the view. Here is an example that you can test with Live Previews:
import SwiftUI
struct UpdateTextView: View {
#State var textToUpdate = "Update me!"
var body: some View {
VStack {
Button(action: {
self.textToUpdate = "I've been udpated!"
}) {
Text("SignUp")
}
Text(textToUpdate)
}
}
}
struct UpdateTextView_Previews: PreviewProvider {
static var previews: some View {
UpdateTextView()
}
}
If your string is stored in a class that is external to the view you can use implement the ObservableObject protocol on your class and make the string variable #Published so that any change to it will cause an update to the view. In the view you need to make your class variable an #ObservedObject to finish hooking it all up. Here is an example you can play with in Live Previews:
import SwiftUI
class ExternalModel: ObservableObject {
#Published var textToUpdate: String = "Update me!"
func registerRequest() {
// other functionality
textToUpdate = "I've been updated!"
}
}
struct UpdateTextViewExternal: View {
#ObservedObject var viewModel: ExternalModel
var body: some View {
VStack {
Button(action: {
self.viewModel.registerRequest()
}) {
Text("SignUp")
}
Text(self.viewModel.textToUpdate)
}
}
}
struct UpdateTextViewExternal_Previews: PreviewProvider {
static var previews: some View {
UpdateTextViewExternal(viewModel: ExternalModel())
}
}

Resources