I'm implementing Form and Picker with SwiftUI. There is a problem that it automatically navigates back to Form screen when I select a Picker option, how to keep it stay in selection screen?
Code:
struct ContentView: View {
#State private var selectedStrength = "Mild"
let strengths = ["Mild", "Medium", "Mature"]
var body: some View {
NavigationView {
Form {
Section {
Picker("Strength", selection: $selectedStrength) {
ForEach(strengths, id: \.self) {
Text($0)
}
}
}
}
.navigationTitle("Select your cheese")
}
}
}
Actual:
Expect: (sample from Iphone Settings)
You may have to make a custom view that mimics what the picker looks like:
struct ContentView: View {
let strengths = ["Mild", "Medium", "Mature"]
#State private var selectedStrength = "Mild"
var body: some View {
NavigationView {
Form {
Section {
NavigationLink(destination: CheesePickerView(strengths: strengths, selectedStrength: $selectedStrength)) {
HStack {
Text("Strength")
Spacer()
Text(selectedStrength)
.foregroundColor(.gray)
}
}
}
}
.navigationTitle("Select your cheese")
}
}
}
struct CheesePickerView: View {
let strengths: [String]
#Binding var selectedStrength: String
var body: some View {
Form {
Section {
ForEach(0..<strengths.count){ index in
HStack {
Button(action: {
selectedStrength = strengths[index]
}) {
HStack{
Text(strengths[index])
.foregroundColor(.black)
Spacer()
if selectedStrength == strengths[index] {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
}.buttonStyle(BorderlessButtonStyle())
}
}
}
}
}
}
Related
I am trying to make a SwiftUI ScrollView scroll to a certain point in an abstracted view when a button is pressed in a view which is calling the abstracted view programmatically. Here is my code:
struct AbstractedView: View {
#Namespace var view2ID
var body: some View {
ScrollView {
VStack {
View1()
View2()
.id(view2ID)
View3()
}
}
}
func scrollToView2(_ proxy: ScrollViewProxy) {
proxy.scrollTo(view2ID, anchor: .topTrailing)
}
}
As you can see, when scrollToView2() is called (in a ScrollViewReader), the AbstractedView scrolls to view2ID. I am creating a number of AbstractedView's programmatically in a different View:
struct HigherView: View {
var numAbstractedViewsToMake: Int
var body: some View {
VStack {
HStack {
ForEach (0..<numAbstractedViewsToMake, id: \.self) { _ in
AbstractedView()
}
}
Text("button")
.onTapGesture {
/* call each AbstractedView.scrollToView2()
}
}
}
}
If I stored these views in an array in a struct inside my HigherView with a ScrollViewReader for each AbstractedView would that work? I feel as though there has to be a nicer way to achieve this, I just have no clue how to do it. I am new to Swift so thank you for any help.
P.S. I have heard about UIKit but I don't know anything about it, is this the right time to be using that?
Using the comments from #Asperi and #jnpdx, I was able to come up with a more powerful solution than I needed:
class ScrollToModel: ObservableObject {
enum Action {
case end
case top
}
#Published var direction: Action? = nil
}
struct HigherView: View {
#StateObject var vm = ScrollToModel()
var numAbstractedViewsToMake: Int
var body: some View {
VStack {
HStack {
Button(action: { vm.direction = .top }) { // < here
Image(systemName: "arrow.up.to.line")
.padding(.horizontal)
}
Button(action: { vm.direction = .end }) { // << here
Image(systemName: "arrow.down.to.line")
.padding(.horizontal)
}
}
Divider()
HStack {
ForEach(0..<numAbstractedViewsToMake, id: \.self) { _ in
ScrollToModelView(vm: vm)
}
}
}
}
}
struct AbstractedView: View {
#ObservedObject var vm: ScrollToModel
let items = (0..<200).map { $0 } // this is his demo
var body: some View {
VStack {
ScrollViewReader { sp in
ScrollView {
LazyVStack { // this bit can be changed accordingly
ForEach(items, id: \.self) { item in
VStack(alignment: .leading) {
Text("Item \(item)").id(item)
Divider()
}.frame(maxWidth: .infinity).padding(.horizontal)
}
}.onReceive(vm.$direction) { action in
guard !items.isEmpty else { return }
withAnimation {
switch action {
case .top:
sp.scrollTo(items.first!, anchor: .top)
case .end:
sp.scrollTo(items.last!, anchor: .bottom)
default:
return
}
}
}
}
}
}
}
}
Thank you both!
I have the following example in SwiftUI:
import SwiftUI
struct DetailView: View {
var element:Int
#Binding var favList:[Int]
var body: some View {
Button(action: {
if !favList.contains(element){
favList.append(element)
}
else{
favList.removeAll(where: {$0 == element})
}
}){
HStack {
Image(systemName: (favList.contains(element)) ? "star.slash" : "star")
Text((favList.contains(element)) ? "Remove from favorites" : "Add to favorites")
}
.frame(maxWidth: 300)
}
}
}
struct MainView: View {
let elements = [1,2,3,4]
#State var favList:[Int] = []
var body: some View {
NavigationView {
List{
if !favList.isEmpty{
Section("Favorits"){
ForEach(elements, id: \.self){element in
if favList.contains(element){
NavigationLink(destination: DetailView(element: element, favList: $favList)) {
Text("\(element)")
}
}
}
}
}
Section("All elements"){
ForEach(elements, id: \.self){element in
if !favList.contains(element){
NavigationLink(destination: DetailView(element: element, favList: $favList)) {
Text("\(element)")
}
}
}
}
}
}
}
}
If I change the favList in the DetailView the view gets automatically dismissed. I guess this because the List structure changes.
Am I doing something wrong? Is this the intended behavior? How can I avoid this?
Best regards
i tried with this, it's the same code just fixing the section header. using the fav button in the view doesn't dismiss the view
import SwiftUI
struct DetailView: View {
var element:Int
#Binding var favList:[Int]
var body: some View {
Button(action: {
if !favList.contains(element){
favList.append(element)
}
else{
favList.removeAll(where: {$0 == element})
}
}){
HStack {
Image(systemName: (favList.contains(element)) ? "star.slash" : "star")
Text((favList.contains(element)) ? "Remove from favorites" : "Add to favorites")
}
.frame(maxWidth: 300)
}
}
}
struct MainView: View {
let elements = [1,2,3,4]
#State var favList:[Int] = []
var body: some View {
NavigationView {
List{
if !favList.isEmpty{
Section(header:Text("Favorits")){
ForEach(elements, id: \.self){element in
if favList.contains(element){
NavigationLink(destination: DetailView(element: element, favList: $favList)) {
Text("\(element)")
}
}
}
}
}
Section(header:Text("Favorits")){
ForEach(elements, id: \.self){element in
if !favList.contains(element){
NavigationLink(destination: DetailView(element: element, favList: $favList)) {
Text("\(element)")
}
}
}
}
}
}
}
}
Tried it on xcode 12,5 with iOS 14 as target
I'm new in SwiftUI. I would like on the top left in the corner a Button next to the Picker. But when I place the Button, the Picker moves to the right and the Button is to nearly on the edge. How can I place the Button flush over the Headline and the Picker perfectly in the middle from iPhone Nodge?
Before:
After:
import SwiftUI
import Combine
struct ContentView: View {
#State var Selection = UserDefaults.standard.integer(forKey: "Picker")
#State var Detail = false
var body: some View {
VStack {
HStack {
Button(action: {
self.Detail.toggle()
}) {
Text("click")
}.sheet(isPresented: $Detail) {
SettingView(showSheetView: self.$Detail, selection: $Selection)
}
Picker("", selection: $Selection) {
Text("Selection1").tag(0)
Text("Selection2").tag(1)
}
.pickerStyle(SegmentedPickerStyle()).padding(.horizontal, 89)
.onReceive(Just(Selection)) {
UserDefaults.standard.set($0, forKey: "Picker")
}
}
PageOne()
}
}
}
struct PageOne: View {
var body: some View {
NavigationView {
VStack {
Text("some Text")
}.navigationTitle("Headline")
}
}
}
struct SettingView: View {
#Binding var showSheetView: Bool
#Binding var selection: Int
var body: some View {
NavigationView {
Text("Test")
.navigationBarTitle(Text("Select something"))
.navigationBarItems(trailing: Button(action: {
self.showSheetView = false
}) {
Text("Ok")
.bold()
})
}
}
}
you can use a ZStack to show your Button and Picker without pushing them.
something like this:
var body: some View {
VStack {
ZStack {
HStack {
Button(action: {
self.Detail.toggle()
}) {
Text("click")
}.sheet(isPresented: $Detail) {
SettingView(showSheetView: self.$Detail, selection: $Selection)
}
Spacer()
}
HStack {
Spacer()
Picker("", selection: $Selection) {
Text("Selection1").tag(0)
Text("Selection2").tag(1)
}
.pickerStyle(SegmentedPickerStyle()).padding(.horizontal, 89)
.onReceive(Just(Selection)) {
UserDefaults.standard.set($0, forKey: "Picker")
}
Spacer()
}
}
PageOne()
}
}
I´m new in SwiftUI and I have the following problem. When I click the button on the iPhone the following comes up
but when I press the button on the iPad the following comes up
The picker is not on the whole sheet. How can I show the picker on the iPad on the whole sheet like on the iPhone?
struct ContentView: View {
#State var value_2 = 1
#State var show_1 = false
var body: some View {
VStack {
Button(action: {
self.show_1.toggle()
}) {
Text("Push")
}
.sheet(isPresented: $show_1) {
Sheet(show_0: self.$show_1, value_1: self.$value_2)
}
}
}
}
struct Sheet: View {
#Binding var show_0: Bool
#Binding var value_1: Int
var body: some View {
NavigationView {
number(value_0: $value_1)
.navigationBarTitle(Text("Enter number"), displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
self.show_0 = false
}) {
Text("Done").bold()
})
}
}
}
struct number: View {
#Binding var value_0: Int
var body: some View {
Section {
Text("Headline")
Picker("",selection: $value_0)
{
ForEach(1..<101) { value in
Text("\(value)")
}
}
}
.labelsHidden()
}
}
As far as I understood your intention, you need just change a style of navigation view, like
var body: some View {
NavigationView {
number(value_0: $value_1)
.navigationBarTitle(Text("Enter number"), displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
self.show_0 = false
}) {
Text("Done").bold()
})
}
.navigationViewStyle(StackNavigationViewStyle()) // << here !!
}
I have an issue using a sheet inside a ForEach. Basically I have a List that shows many items in my array and an image that trigger the sheet. The problem is that when my sheet is presented it only shows the first item of my array which is "Harry Potter" in this case.
Here's the code
struct ContentView: View {
#State private var showingSheet = false
var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
var body: some View {
NavigationView {
List {
ForEach(0 ..< movies.count) { movie in
HStack {
Text(self.movies[movie])
Image(systemName: "heart")
}
.onTapGesture {
self.showingSheet = true
}
.sheet(isPresented: self.$showingSheet) {
Text(self.movies[movie])
}
}
}
}
}
}
There should be only one sheet, so here is possible approach - use another sheet modifier and activate it by selection
Tested with Xcode 12 / iOS 14 (iOS 13 compatible)
extension Int: Identifiable {
public var id: Int { self }
}
struct ContentView: View {
#State private var selectedMovie: Int? = nil
var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
var body: some View {
NavigationView {
List {
ForEach(0 ..< movies.count) { movie in
HStack {
Text(self.movies[movie])
Image(systemName: "heart")
}
.onTapGesture {
self.selectedMovie = movie
}
}
}
.sheet(item: self.$selectedMovie) {
Text(self.movies[$0])
}
}
}
}
I changed your code to have only one sheet and have the selected movie in one variable.
extension String: Identifiable {
public var id: String { self }
}
struct ContentView: View {
#State private var selectedMovie: String? = nil
var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
var body: some View {
NavigationView {
List {
ForEach(movies) { movie in
HStack {
Text(movie)
Image(systemName: "heart")
}
.onTapGesture {
self.selectedMovie = movie
}
}
}
.sheet(item: self.$selectedMovie, content: { selectedMovie in
Text(selectedMovie)
})
}
}
}
Wanted to give my 2 cents on the matter.
I was encountering the same problem and Asperi's solution worked for me.
BUT - I also wanted to have a button on the sheet that dismisses the modal.
When you call a sheet with isPresented you pass a binding Bool and so you change it to false in order to dismiss.
What I did in the item case is I passed the item as a Binding. And in the sheet, I change that binding item to nil and that dismissed the sheet.
So for example in this case the code would be:
var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
var body: some View {
NavigationView {
List {
ForEach(0 ..< movies.count) { movie in
HStack {
Text(self.movies[movie])
Image(systemName: "heart")
}
.onTapGesture {
self.selectedMovie = movie
}
}
}
.sheet(item: self.$selectedMovie) {
Text(self.movies[$0])
// My addition here: a "Done" button that dismisses the sheet
Button {
selectedMovie = nil
} label: {
Text("Done")
}
}
}
}