Let's say I have this simple Picker in a SegmentedPickerStyle.
struct ContentView: View {
#State private var favoriteColor = 0
var body: some View {
VStack {
Picker(selection: $favoriteColor, label: Text("bla bla...")) {
Text("This is a very long string").tag(0)
Text("B").tag(1)
Text("C").tag(2)
}
.pickerStyle(SegmentedPickerStyle())
}
}
}
the result will be something like this:
Is there a way to make the size of the segments dynamic? So that the first segment will be bigger and "B" and "C" are adjusted to it?
Related
I have a ContentView that has a state variable "count". When its value is changed, the number of stars in the child view should be updated.
struct ContentView: View {
#State var count: Int = 5
var body: some View {
VStack {
Stepper("Count", value: $count, in: 0...10)
StarView(count: $count)
}
.padding()
}
}
struct StarView: View {
#Binding var count: Int
var body: some View {
HStack {
ForEach(0..<count) { i in
Image(systemName: "star")
}
}
}
}
I know why the number of stars are not changed in the child view, but I don't know how to fix it because the child view is in a package that I cannot modify. How can I achieve my goal only by changing the ContentView?
You are using the incorrect ForEach initializer, because you aren't explicitly specifying an id. This initializer is only for constant data, AKA a constant range - which this data isn't since count changes.
The documentation for the initializer you are currently using:
It's important that the id of a data element doesn't change unless you
replace the data element with a new data element that has a new
identity. If the id of a data element changes, the content view
generated from that data element loses any current state and animations.
Explicitly set the id like so, by adding id: \.self:
struct StarView: View {
#Binding var count: Int
var body: some View {
HStack {
ForEach(0 ..< count, id: \.self) { _ in
Image(systemName: "star")
}
}
}
}
Similar answer here.
New to SwiftUI.
I want to change the string calBudget in the UserSettings view and convert it to Int in the ContentView. the first problem is the Integer conversion is not working with my code. The second problem is, every keystroke in the UserSettings view is generating a new UserSettings view creating a bunch of nested views.
struct ContentView: View {
#AppStorage("calBudget") var calBudget = "1700"
#AppStorage("calsBudget") var calsBudget = 0
var body: some View {
NavigationView {
Form {
Text("Budget: \(self.calBudget)")
Text("to integer \(String(self.calsBudget))")
}.toolbar {
ToolbarItem() {
NavigationLink( destination: UserSettings(calBudget: $calBudget, calsBudget: $calsBudget)) { Text("settings") }
}
}
}
}
}
struct UserSettings: View {
#Binding var calBudget: String
#Binding var calsBudget: Int
var body: some View {
Form {
HStack {
TextField("Budget: ", text: self.$calBudget)
Button(action: {
let calsBudget: Int = Int(self.calBudget ) ?? 1000
}) { Text("make into integer")}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView( )
}
}
the first problem is the Integer conversion is not working with my code
This is your code currently:
let calsBudget: Int = Int(self.calBudget ) ?? 1000
Here, you're creating a new constant, calsBudget. Then you do nothing with it, throwing it away. Instead, you want to modify the existing #Binding var calsBudget: Int, so assign the value.
calsBudget = Int(self.calBudget ) ?? 1000
The second problem is, every keystroke in the UserSettings view is generating a new UserSettings view creating a bunch of nested views
This happens because of this code:
.toolbar {
ToolbarItem() {
NavigationLink( destination: UserSettings(calBudget: $calBudget, calsBudget: $calsBudget)) { Text("settings") }
}
}
NavigationLink must always be inside a NavigationView. Whenever you put it outside, for example in a toolbar, you'll run into weird issues.
Here's the fixed code:
struct ContentView: View {
#AppStorage("calBudget") var calBudget = "1700"
#AppStorage("calsBudget") var calsBudget = 0
#State var settingsPresented = false
var body: some View {
NavigationView {
/// NavigationView must only contain 1 view, so wrap Form and NavigationLink inside VStack
VStack {
Form {
Text("Budget: \(self.calBudget)")
Text("to integer \(String(self.calsBudget))")
}
/// NavigationLink must be inside NavigationView
NavigationLink(
destination: UserSettings(calBudget: $calBudget, calsBudget: $calsBudget),
isActive: $settingsPresented) /// activates when `settingsPresented` is true
{ EmptyView() }
}
.toolbar {
ToolbarItem() {
Button("settings") {
settingsPresented = true /// activate the NavigationLink
}
}
}
}
}
}
struct UserSettings: View {
#Binding var calBudget: String
#Binding var calsBudget: Int
var body: some View {
Form {
HStack {
TextField("Budget: ", text: self.$calBudget)
Button(action: {
calsBudget = Int(self.calBudget ) ?? 1000 /// assign value, not create
}) { Text("make into integer")}
}
}
}
}
I have very simple "app" in SwiftUI
How i can passing stepper value from list to struct SumOfValue or to ContentView ? But i want passing sum of stepper value in case from image will be 8.
struct ContentView: View {
var body: some View {
VStack{
List{
ProductList()
ProductList()
}
Spacer()
Text("Sum of stepper value: ?????")
.padding(.bottom, 50
)
SumOfValue()
}
}
}
struct ProductList:View {
#State var stepperValueTest: Int = 0
var body: some View {
HStack {
Stepper("Value: \(stepperValueTest)", value: $stepperValueTest)
}
}
}
struct SumOfValue: View {
var body: some View {
Text("or here sum of value: ????? ")
.foregroundColor(Color.red)
}
}
I try use #Binding but didn`t work.
There are multiple approaches here, and it's ultimately a question of data organization.
One way to think about is that there is an array of values that a parent - ContentView in your case - "owns", and each child updates their allotted value in that array using a binding. This way, the parent can easily calculate the sum of these values since it has the entire array.
struct ContentView: View {
#State var values = [0,0,0]
var body: some View {
VStack {
List {
ProductList(stepperValueTest: $values[0])
ProductList(stepperValueTest: $values[1])
ProductList(stepperValueTest: $values[2])
}
Text("Sum: \(sum)")
}
}
var sum: Int { values.reduce(0, +) }
}
struct ProductList:View {
#Binding var stepperValueTest: Int // change to Binding
var body: some View {
HStack {
Stepper("Value: \(stepperValueTest)", value: $stepperValueTest)
}
}
}
The #State goes in the parent (ContentView), and the #Binding goes in the child (ProductList and SumOfValue).
Try this:
struct ContentView: View {
/// States here!
#State var firstStepperValue: Int = 0
#State var secondStepperValue: Int = 0
var body: some View {
VStack{
List{
/// pass it in here!
ProductList(stepperValueTest: $firstStepperValue)
ProductList(stepperValueTest: $secondStepperValue)
}
Spacer()
/// add the values here
Text("Sum of stepper value: \(firstStepperValue + secondStepperValue)")
.padding(.bottom, 50
)
/// you can also pass it in here
SumOfValue(first: $firstStepperValue, second: $secondStepperValue)
.padding(.bottom, 100)
}
}
}
struct ProductList:View {
/// Binding here!
#Binding var stepperValueTest: Int
var body: some View {
HStack {
Stepper("Value: \(stepperValueTest)", value: $stepperValueTest)
}
}
}
struct SumOfValue: View {
#Binding var first: Int
#Binding var second: Int
var body: some View {
Text("or here sum of value: \(first + second) ")
.foregroundColor(Color.red)
}
}
Result:
I have a navigation stack that's not quite working as desired.
From my main view, I want to switch over to a list view which for the sake of this example represents an array of strings.
I want to then navigate to a detail view, where I want to be able to change the value of the selected string.
I have 2 issues with below code:
on the very first keystroke within the TextField, the detail view is being dismissed
the value itself is not being changed
Also, I suppose there must be a more convenient way to do the binding in the detail view ...
Here's the code:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
TestMainView()
}
}
}
struct TestMainView: View {
var body: some View {
NavigationView {
List {
NavigationLink("List View", destination: TestListView())
}
.navigationTitle("Test App")
}
}
}
struct TestListView: View {
#State var strings = [
"Foo",
"Bar",
"Buzz"
]
#State var selectedString: String? = nil
var body: some View {
List(strings.indices) { index in
NavigationLink(
destination: TestDetailView(selectedString: $selectedString),
tag: strings[index],
selection: $selectedString) {
Text(strings[index])
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("List")
}
}
}
struct TestDetailView: View {
#Binding var selectedString: String?
var body: some View {
VStack {
if let _ = selectedString {
TextField("Placeholder",
text: Binding<String>( //what's a better solution here?
get: { selectedString! },
set: { selectedString = $0 }
)
)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
}
Spacer()
}
.navigationTitle("Detail")
}
}
struct TestMainView_Previews: PreviewProvider {
static var previews: some View {
TestMainView()
}
}
I am quite obviously doing it wrong, but I cannot figure out what to do differently...
You're changing the NavigationLink's selection from inside the NavigationLink which forces the TestListView to reload.
You can try the following instead:
struct TestListView: View {
#State var strings = [
"Foo",
"Bar",
"Buzz",
]
var body: some View {
List(strings.indices) { index in
NavigationLink(destination: TestDetailView(selectedString: self.$strings[index])) {
Text(self.strings[index])
}
}
}
}
struct TestDetailView: View {
#Binding var selectedString: String // remove optional
var body: some View {
VStack {
TextField("Placeholder", text: $selectedString)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
Spacer()
}
}
}
I have a struct which shuffles and Lists records from CoreData.
I would like to reload / Refresh the List view with a Button.
I tried to use a function from within the Button.
Is there a way I can do this?
var body: some View {
VStack {
List {
ForEach(dictionary.shuffled().prefix(upTo: 10),id: \.self) { word in
HStack {
Text("\(word.englishWord)")
.foregroundColor(Color.blue)
Text("| \(word.urhoboWord) |")
.foregroundColor(Color.green)
Image(word.imageName)
.resizable()
.frame(width:40, height: 40)
}//HStack
}//End of ForEach
}//End of List
//Button to reload and shuffle list
Button(action: {}) {
Text("Shuffle")
.padding()
.background(Color.black)
.foregroundColor(Color.white)
.cornerRadius(6)
}
.navigationBarTitle(Text("Begin Learning"),displayMode: .inline)
Just trigger any value of the #State or #Published of #ObservableObject.
If you do not have such, just create one:
#State var refresh: Bool = false
func update() {
refresh.toggle()
}
You should move this dictionary.shuffled().prefix(upTo: 10) to your ViewModel and your view just reload base on the data.
Take a look at this code for reference:
struct SampleShuffleView : View {
#ObservedObject var viewModel : ShuffleViewModel = ShuffleViewModel()
var body : some View {
VStack {
List(self.viewModel.listData, id: \.self) { str in
Text(str)
}
Button(action: self.shuffle) {
Text("Shuffle me").padding()
}.background(Color.white).padding()
}
}
func shuffle() {
self.viewModel.shuffle()
}
}
class ShuffleViewModel : ObservableObject {
#Published var listData = ["one", "two", "three", "four"]
func shuffle() {
listData.shuffle()
//or listData = dictionary.shuffled().prefix(upTo: 10)
}
}
Note: All view's components will be reloaded when #ObservedObject changes, so consider to separate smaller view-viewmodel(s), or using #State variable.
Hope this helps.
Think about. To show array and shuffle on tap, do exactly what you would like to see. first show us the array in some "list" like manner and next shuffle it on user action.
struct ContentView: View {
#State var arr = ["ALFA", "BETA", "GAMA", "DELTA"]
var body: some View {
VStack {
VStack {
Divider()
ForEach(arr, id: \.self) { element in
VStack {
Text(element)
Divider()
}
}
}
Spacer()
Button(action: {
self.arr.shuffle()
}) {
Text("Shuffle")
}
Spacer()
}
}
}
arr.shuffle() changed the #State of View and force SwiftUI to "reload it" automatically.