Swiftui: align selected text in picker to leading edge - swiftui

Currently I've a picker included in a Section included in a Form what I'm trying to reach is to align the selected value of the picker to the leading in both iOS 13 and 14, I've tried many solutions such as labelsHidden() but with no result, kindly find the code sample that generates the following screenshot on iOS 14, any help would be appreciated
struct ContentView: View {
#State private var selectedStrength = "Mild"
let strengths = ["Mild", "Medium", "Mature"]
var body: some View {
NavigationView {
Form {
Section {
Picker("", selection: $selectedStrength) {
ForEach(strengths, id: \.self) {
Text($0)
}
}
}
}
}
}
}

Use the Text() with a Spacer() in a HStack()
struct ContentView: View {
#State private var selectedStrength = "Mild"
let strengths = ["Mild", "Medium", "Mature"]
var body: some View {
NavigationView {
Form {
Section {
Picker("", selection: $selectedStrength) {
ForEach(strengths, id: \.self) { t in
HStack {
Text(t)
Spacer()
}
}
}
}
}
}
}
}

You have to use .frame() and .labelsHidden()
struct ContentView: View {
#State private var selectedStrength = "Mild"
let strengths = ["Mild", "Medium", "Mature"]
var body: some View {
NavigationView {
Form {
Section {
Picker("", selection: $selectedStrength) {
ForEach(strengths, id: \.self) {
Text($0)
}
}
.frame(width: 160, alignment: .leading)
.labelsHidden()
}
}
}
}
}
tested on IOS 16

Related

On tap gesture on an image SwiftUI

So I am trying to change between views when someone clicks on the image, but the action after the ontapgesture is always "expression is unused". I tried changing it to a navigation view as well as other things but I feel like nothing seems to be working.
Code below:
struct MenuView2: View {
#State private var menuu2 = menu2()
var body: some View {
ScrollView(){
VStack {
ZStack {
Rectangle().frame(height:40).opacity(0.25).blur(radius: 10).onTapGesture {
print("breakfast tapped ")
}
HStack {
VStack(alignment: .leading, spacing: 8, content: {
Text("Breakfast").font(.largeTitle)
})
}
}
Image("breakfast").resizable().scaledToFill().onTapGesture {
menuu2
}
}
}
}
}
Thank you.
The error you get is "correct" in that menuu2 does not do anything, it is just there.
There are a number of ways to change view on tap, this is just one way:
struct MenuView2: View {
#State private var menuu2 = menuu2()
#State private var changeView = false
var body: some View {
changeView ? AnyView(theOtherView) : AnyView(theScrollView)
}
var theOtherView: some View {
// menuu2 presumably
// just for testing
Text("theOtherView").onTapGesture {
self.changeView = false
}
}
var theScrollView: some View {
ScrollView() {
VStack {
ZStack {
Rectangle().frame(height:40).opacity(0.25).blur(radius: 10).onTapGesture {
print("breakfast tapped ")
}
HStack {
VStack(alignment: .leading, spacing: 8, content: {
Text("Breakfast").font(.largeTitle)
})
}
}
Image("breakfast").resizable().scaledToFill().onTapGesture {
self.changeView = true
}
}
}
}
}

How to Add multi text into the list in SwiftUI?(Data Flow)

I'm trying to build an demo app by swiftUI that get multi text from user and add them to the list, below , there is an image of app every time user press plus button the AddListView show to the user and there user can add multi text to the List.I have a problem to add them to the list by new switUI data Flow I don't know how to pass data.(I comment more information)
Thanks 🙏
here is my code for AddListView:
import SwiftUI
struct AddListView: View {
#State var numberOfTextFiled = 1
#Binding var showAddListView : Bool
var body: some View {
ZStack {
Title(numberOfTextFiled: $numberOfTextFiled)
VStack {
ScrollView {
ForEach(0 ..< numberOfTextFiled, id: \.self) { item in
PreAddTextField()
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView)
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
AddListView(showAddListView: .constant(false))
}
}
struct PreAddTextField: View {
// I made this standalone struct and use #State to every TextField text be independent
// if i use #Binding to pass data all Texfield have the same text value
#State var textInTextField = ""
var body: some View {
VStack {
TextField("Enter text", text: $textInTextField)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
// What should happen here to add Text to List???
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
#Binding var numberOfTextFiled : Int
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
numberOfTextFiled += 1
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
and for DataModel:
import SwiftUI
struct Text1 : Identifiable , Hashable{
var id = UUID()
var text : String
}
var textData = [
Text1(text: "SwiftUI"),
Text1(text: "Data flow?"),
]
and finally:
import SwiftUI
struct ListView: View {
#State var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(textData, id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
Because of the multiple-items part of the question, this becomes a lot less trivial. However, using a combination of ObservableObjects and callback functions, definitely doable. Look at the inline comments in the code for explanations about what is going on:
struct Text1 : Identifiable , Hashable{
var id = UUID()
var text : String
}
//Store the items in an ObservableObject instead of just in #State
class AppState : ObservableObject {
#Published var textData : [Text1] = [.init(text: "Item 1"),.init(text: "Item 2")]
}
//This view model stores data about all of the new items that are going to be added
class AddListViewViewModel : ObservableObject {
#Published var textItemsToAdd : [Text1] = [.init(text: "")] //start with one empty item
//save all of the new items -- don't save anything that is empty
func saveToAppState(appState: AppState) {
appState.textData.append(contentsOf: textItemsToAdd.filter { !$0.text.isEmpty })
}
//these Bindings get used for the TextFields -- they're attached to the item IDs
func bindingForId(id: UUID) -> Binding<String> {
.init { () -> String in
self.textItemsToAdd.first(where: { $0.id == id })?.text ?? ""
} set: { (newValue) in
self.textItemsToAdd = self.textItemsToAdd.map {
guard $0.id == id else {
return $0
}
return .init(id: id, text: newValue)
}
}
}
}
struct AddListView: View {
#Binding var showAddListView : Bool
#ObservedObject var appState : AppState
#StateObject private var viewModel = AddListViewViewModel()
var body: some View {
ZStack {
Title(addItem: { viewModel.textItemsToAdd.append(.init(text: "")) })
VStack {
ScrollView {
ForEach(viewModel.textItemsToAdd, id: \.id) { item in //note this is id: \.id and not \.self
PreAddTextField(textInTextField: viewModel.bindingForId(id: item.id))
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView, save: {
viewModel.saveToAppState(appState: appState)
})
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct PreAddTextField: View {
#Binding var textInTextField : String //this takes a binding to the view model now
var body: some View {
VStack {
TextField("Enter text", text: $textInTextField)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var save : () -> Void //callback function for what happens when "Add" gets pressed
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
save()
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
var addItem : () -> Void //callback function for what happens when the plus button is hit
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
addItem()
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
struct ListView: View {
#StateObject var appState = AppState() //store the AppState here
#State private var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(appState.textData, id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView, appState: appState)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}

Popover displaying inaccurate information inside ForEach

I'm having a problem where I have a ForEach loop inside a NavigationView. When I click the Edit button, and then click the pencil image at the right hand side on each row, I want it to display the text variable we are using from the ForEach loop. But when I click the pencil image for the text other than test123, it still displays the text test123 and I have absolutely no idea why.
Here's a video. Why is this happening?
import SwiftUI
struct TestPopOver: View {
private var stringObjects = ["test123", "helloworld", "reddit"]
#State private var editMode: EditMode = .inactive
#State private var showThemeEditor = false
#ViewBuilder
var body: some View {
NavigationView {
List {
ForEach(self.stringObjects, id: \.self) { text in
NavigationLink( destination: HStack{Text("Test!")}) {
HStack {
Text(text)
Spacer()
if self.editMode.isEditing {
Image(systemName: "pencil.circle").imageScale(.large)
.onTapGesture {
if self.editMode.isEditing {
self.showThemeEditor = true
}
}
}
}
}
.popover(isPresented: $showThemeEditor) {
CustomPopOver(isShowing: $showThemeEditor, text: text)
}
}
}
.navigationBarTitle("Reproduce Editing Bug!")
.navigationBarItems(leading: EditButton())
.environment(\.editMode, $editMode)
}
}
}
struct CustomPopOver: View {
#Binding var isShowing: Bool
var text: String
var body: some View {
VStack(spacing: 0) {
HStack() {
Spacer()
Button("Cancel") {
self.isShowing = false
}.padding()
}
Divider()
List {
Section {
Text(text)
}
}.listStyle(GroupedListStyle())
}
}
}
This is a very common issue (especially since iOS 14) that gets run into a lot with sheet but affects popover as well.
You can avoid it by using popover(item:) rather than isPresented. In this scenario, it'll actually use the latest values, not just the one that was present when then view first renders or when it is first set.
struct EditItem : Identifiable { //this will tell it what sheet to present
var id = UUID()
var str : String
}
struct ContentView: View {
private var stringObjects = ["test123", "helloworld", "reddit"]
#State private var editMode: EditMode = .inactive
#State private var editItem : EditItem? //the currently presented sheet -- nil if no sheet is presented
#ViewBuilder
var body: some View {
NavigationView {
List {
ForEach(self.stringObjects, id: \.self) { text in
NavigationLink( destination: HStack{Text("Test!")}) {
HStack {
Text(text)
Spacer()
if self.editMode.isEditing {
Image(systemName: "pencil.circle").imageScale(.large)
.onTapGesture {
if self.editMode.isEditing {
self.editItem = EditItem(str: text) //set the current item
}
}
}
}
}
.popover(item: $editItem) { item in //item is now a reference to the current item being presented
CustomPopOver(text: item.str)
}
}
}
.navigationBarTitle("Reproduce Editing Bug!")
.navigationBarItems(leading: EditButton())
.environment(\.editMode, $editMode)
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct CustomPopOver: View {
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var text: String
var body: some View {
VStack(spacing: 0) {
HStack() {
Spacer()
Button("Cancel") {
self.presentationMode.wrappedValue.dismiss()
}.padding()
}
Divider()
List {
Section {
Text(text)
}
}.listStyle(GroupedListStyle())
}
}
}
I also opted to use the presentationMode environment property to dismiss the popover, but you could pass the editItem binding and set it to nil as well (#Binding var editItem : EditItem? and editItem = nil). The former is just a little more idiomatic.

Swiftui: ForEach button, wrong parameter is passed in a view [duplicate]

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")
}
}
}
}

Sidebar with different columns in SwiftUI

I'm playing with the new Sidebar that has come with SwiftUI 2 and the possibility to navigate in large screens with three columns. An example about how it works can be found here: https://www.hackingwithswift.com/quick-start/swiftui/how-to-add-a-sidebar-for-ipados
It works fine, but I would like to go one step forward and make some options of my main menu that show the three columns but other options just two.
Here an example of some demo code.
import SwiftUI
struct ContentView: View {
var body: some View{
NavigationView{
List{
Section(header: Text("Three columns")){
NavigationLink(
destination: ItemsView(),
label: {
Label("Animals",systemImage: "tortoise")
})
NavigationLink(
destination: ItemsView(),
label: {
Label("Animals 2",systemImage: "hare")
})
}
Section(header: Text("Two columns")){
NavigationLink(
destination: Text("I want to see here a single view, without detail"),
label: {
Label("Settings",systemImage: "gear")
})
NavigationLink(
destination: Text("I want to see here a single view, without detail"),
label: {
Label("Settings 2",systemImage: "gearshape")
})
}
}
.listStyle(SidebarListStyle())
.navigationBarTitle("App Menu")
ItemsView()
DetailView(animal: "default")
}
}
}
struct ItemsView: View{
let animals = ["Dog", "Cat", "Lion", "Squirrel"]
var body: some View{
List{
ForEach(animals, id: \.self){ animal in
NavigationLink(
destination: DetailView(animal: animal)){
Text(animal)
}
}
}
.listStyle(PlainListStyle())
.navigationTitle("Animals")
}
}
struct DetailView: View{
var animal: String
var body: some View{
VStack{
Text("🐕")
.font(.title)
.padding()
Text(animal)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.sizeThatFits)
}
}
If you run the code in, for example, an iPad Pro (12,9-inch) in landscape mode, you can see the tree columns. First the App Menu (sidebar). If you click on one of the first two options (animals, and animals 2), you can see a list of animals (second column) and when you click on some animal, you reach the third column (detail view).
However, I want to have only two columns when I click on the last two options of the menu (Settings and Settings 2). Any clue how to achieve it?
I've tried to hide that section if some of the first options in menu are not selected (with the selected parameter in NavigationLink), but without luck. It seems it is not possible (or I don't know) to know which option is selected in the sidebar.
Any idea is welcome!
It took me a few days to figure it out.
Test on different iPad device & multiple tasking mode, all works as expected.(iOS14+, haven't test on iOS13)
Minimal Example:
extension UISplitViewController {
open override func viewDidLoad() {
super.viewDidLoad()
self.show(.primary) // show sidebar, this is the key, toke me days to find this...
self.showsSecondaryOnlyButton = true
}
}
struct ContentView: View {
#State var column: Int = 3
var body: some View {
switch column {
case 3: // Triple Column
NavigationView {
List {
HStack {
Text("Triple")
}
.onTapGesture {
column = 3
}
HStack {
Text("Double")
}
.onTapGesture {
column = 2
}
}
Text("Supplementary View")
Text("Detail View")
}
default: // Double Column
NavigationView {
List {
HStack {
Text("Triple")
}
.onTapGesture {
column = 3
}
HStack {
Text("Double")
}
.onTapGesture {
column = 2
}
}
Text("Supplementary View")
}
}
}
}
My another answer: set sidebar default selected item.
Combine with this two solution, I have built an 2&3 column co-exist style's app.
SwiftUI, selecting a row in a List programmatically
Here is a solution which uses a custom ViewModifier. It's working on iOS 14.2, 15.0 and 15.2. Since you are using SidebarListStyle and Label I didn't test for prior versions.
Testproject:
enum Item: Hashable {
case animals, animals2, settings, settings2
static var threeColumns = [Item.animals, .animals2]
static var twoColumns = [Item.settings, .settings2]
var title: String {
switch self {
case .animals:
return "animals"
case .animals2:
return "animals2"
case .settings:
return "settings"
case .settings2:
return "settings2"
}
}
var systemImage: String {
switch self {
case .animals:
return "tortoise"
case .animals2:
return "hare"
case .settings:
return "gear"
case .settings2:
return "gearshape"
}
}
}
struct ContentView: View {
#State var selectedItem: Item?
var body: some View {
NavigationView {
List {
Section(header: Text("Three columns")) {
ForEach(Item.threeColumns, id: \.self) { item in
NavigationLink(tag: item, selection: $selectedItem) {
ItemsView()
} label: {
Label(item.title.capitalized, systemImage: item.systemImage)
}
}
}
Section(header: Text("Two columns")) {
ForEach(Item.twoColumns, id: \.self) { item in
NavigationLink(
destination: Text("I want to see here a single view, without detail"),
label: {
Label(item.title, systemImage: item.systemImage)
})
}
}
}
.listStyle(SidebarListStyle())
.navigationBarTitle("App Menu")
}
}
}
struct ItemsView: View {
let animals = ["Dog", "Cat", "Lion", "Squirrel"]
var body: some View {
List {
ForEach(animals, id: \.self) { animal in
NavigationLink(
destination: DetailView(animal: animal)) {
Text(animal)
}
}
}
.listStyle(PlainListStyle())
.navigationTitle("Animals")
}
}
struct DetailView: View {
var animal: String
var body: some View {
VStack {
Text("🐕")
.font(.title)
.padding()
Text(animal)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.sizeThatFits)
}
}
private struct ColumnModifier: ViewModifier {
let item: Item?
func body(content: Content) -> some View {
if item == .settings || item == .settings2 {
content
EmptyView()
} else {
content
EmptyView()
DetailView(animal: "default")
}
}
}
Suppose you want to create a triple-column view (with two sidebars and a detail view). Here is an example to show Projects in the first column, Files in second column and File Content in the last column.
The core step is to add three View()s in NavagationView { ... }.
import SwiftUI
struct MainView: View {
var body: some View {
ProjectsSidebar()
}
}
struct ProjectsSidebar: View {
var body: some View {
NavigationView {
List {
ForEach([1, 2, 3], id: \.self) { project_id in
VStack {
NavigationLink {
FilesSidebar(project_id: project_id)
.navigationTitle("Project \(project_id)")
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.columns)
} label: {
Text("Project \(project_id)")
}
}
}
}
.listStyle(.sidebar)
.navigationTitle("Projects")
.navigationBarTitleDisplayMode(.inline)
FilesSidebar.DefaultView()
DetailView.DefaultView()
}
.navigationViewStyle(.columns)
}
}
struct FilesSidebar: View {
var project_id: Int
var body: some View {
List {
ForEach([1, 2, 3, 4], id: \.self) { file_id in
NavigationLink {
DetailView(project_id: project_id, file_id: file_id)
.navigationTitle("File")
.navigationBarTitleDisplayMode(.inline)
} label: {
Text("File \(file_id)")
}
}
}
.listStyle(.sidebar)
}
struct DefaultView: View {
var body: some View {
VStack {
Text("Please select a project.")
}
}
}
}
struct DetailView: View {
var project_id: Int
var file_id: Int
var body: some View {
VStack {
Text("Project \(project_id) - File \(file_id)")
}
}
struct DefaultView: View {
var body: some View {
VStack {
Text("Please select a file.")
}
}
}
}
first launch:
triple columns:
select project and file: