Set picker default no clickable value as title - swift

I'm trying to develop a Picker with a field that corresponds to the title. The problem is that I'm not understanding how to use the title field of the Picker view.
This is the code. The problem is that the Picker is taking as title the string "Spain". Instead I want the title "Select country" which is visible until the user select a field.
struct CustomPicker: View {
#State private var selection = "Select country"
let colors = ["Spain", "France"]
var body: some View {
VStack(alignment: .leading, spacing: 4, content: {
HStack(spacing: 15) {
Picker("Select country", selection: $selection) {
ForEach(colors, id: \.self) {
Text($0)
}
}
.pickerStyle(DefaultPickerStyle())
}
.padding(.horizontal, 20)
})
.frame(height: 50)
.background(.white)
.cornerRadius(10)
.padding(.horizontal, 20)
}
}

What you're trying to do doesn't come standard with SwiftUI. You would have to custom make your UI for this (and that might not be hard). Depending on how much you're willing to compromise, you can have what you want with a slight tweak of your code. This is what a picker looks like within a List (as well as your Picker in the List).
To do this I modified your code slightly to include an enum for the countries.
enum Country: String, CaseIterable, Identifiable {
case spain, france, germany, switzerland
var id: Self { self }
}
struct CustomPicker: View {
#State private var selection: Country = .spain
var body: some View {
NavigationView {
List {
Picker("Select Country", selection: $selection) {
ForEach(Country.allCases, id: \.self) {
Text($0.rawValue.capitalized)
.tag($0)
}
}
Picker("Select Country", selection: $selection) {
ForEach(Country.allCases, id: \.self) {
Text($0.rawValue.capitalized)
.tag($0)
}
}
.pickerStyle(.menu)
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Country Picker")
}
}
}

Related

How could I correctly implement favourites in SwiftUI?

I am building an app that will store multiple Nintendo consoles and their details (kinda like Mactracker but for Nintendo stuff).
I wanna store consoles that the user chooses in a favourites category on the main menu but I can't implement it correctly.
I have the main menu which shows the different categories as well as the favourite category which is duplicated for each category:
Each favourites "button" takes me to the favourites of only a specific category (the category beneath it) and I would like that all of them are in only one tab and not in multiple tabs like they are so far.
The favourites category is a bool defined in the Console List which will be listed below.
Thanks for the help and sorry if its a stupid question, I'm quite new to programming.
Main Menu:
struct MainMenu: View {
// Use categories ordered by alphabetical order
var categories = ConsoleList.categories.sorted(by: {$0.key < $1.key})
var body: some View {
// Loop on categories
NavigationView{
List(categories, id:\.key){category in
// The NavigationLink that takes me to the favorites view
NavigationLink(destination: Favorites(con: category.value)){
Image(systemName: "heart")
Text("Favorites")
.fontWeight(.semibold)
}
NavigationLink(destination: ConsoleMenu(con: category.value), label:{
Image(systemName: "folder")
.foregroundColor(.red)
.scaledToFit()
.frame(height: 30)
.cornerRadius(4)
.padding(.vertical, 4)
VStack{
Text(category.key)
.fontWeight(.semibold)
}
})
}
.navigationTitle("")
}
}
}
Favourites menu:
struct Favorites: View {
var con: [ConsoleDetails]
var body: some View {
List(con){ cons in
if cons.favorites {
NavigationLink(destination: ConsoleDetailView(con: cons), label:{
Image(cons.imgName)
.resizable()
.scaledToFit()
.frame(height: 50)
.cornerRadius(4)
.padding(.vertical, 4)
.navigationTitle("\(cons.category)")
VStack (alignment: .leading){
if cons.category == "Game & Watch" {
Text(cons.consoleName)
.fontWeight(.semibold)
Text(cons.mostSoldGame)
.font(.subheadline)
}else{
Text(cons.consoleName)
.fontWeight(.semibold)
}
}
}).navigationTitle("Favorites")
}
}
}
}
struct favorites_Previews: PreviewProvider {
static var previews: some View {
Favorites(con: ConsoleList.consoles)
}
}
The consoles list:
struct ConsoleDetails: Identifiable{
let id = UUID()
var imgName: String = ""
var consoleName: String = ""
var mostSoldGame: String = ""
var initialPrice: String = ""
var ReleaseDate: String = ""
var Discontinuation: String = ""
var category: String = ""
var estimatedPricedToday: String = ""
var cables: String = ""
var favorites: Bool
}
struct ConsoleList{
//The consoles list has more consoles usually but I'll only put one to save space
static var categories = Dictionary(grouping: consoles, by: {$0.category } )
static var favs = Dictionary(grouping: consoles, by: {$0.favorites} )
static var consoles = [
//Current Consoles
ConsoleDetails(imgName: "NS_OG",
consoleName: "Nintendo Switch",
mostSoldGame: "Mario Kart 8 Deluxe",
initialPrice: "299.99",
ReleaseDate: "Mar 3, 2017",
Discontinuation: "Still Available",
category: "Current Consoles",
estimatedPricedToday: "$200-250 used",
cables: "HDMI, USB Type-C",
favorites: true),
Edit: I have added what was proposed but I get an error in the nav link. See below:
List{
Section {
// The NavigationLink that takes me to the favorites view
NavigationLink {
Favorites() // Right Here: Missing argument for parameter 'con' in call
} label: {
Image(systemName: "heart")
Text("Favorites")
.fontWeight(.semibold)
}
}
Section {
ForEach(categories, id: \.key){ category in
NavigationLink(destination: ConsoleMenu(con: category.value), label:{
Image(systemName: "folder")
.foregroundColor(.red)
.scaledToFit()
.frame(height: 30)
.cornerRadius(4)
.padding(.vertical, 4)
VStack{
Text(category.key)
.fontWeight(.semibold)
}
})
}
}
}
Instead of having the list loop over the categories, you could put a ForEach in the List.
Then, you could have one favorites link, and put it in a different section to differentiate it.
List {
Section {
NavigationLink {
Favorites(con: /* (1) */ ConsoleList.consoles)
} label: { /* ... */ }
}
Section {
ForEach(categories, id: \.key) { category in
// ...
}
}
}
(1) You will also need to pass in a list of all the consoles to show.

sth wrong with .searchable

I have added searchable to my SwiftUI List.
But the search TextField isn't showing.
Here is my code:
NavigationView {
List(searchResults, id: \.self) { item in
NavigationLink {
LegendDetailView(item: item)
} label: {
HStack {
Text(item.name).padding(1)
Spacer()
Image(systemName: "chevron.right").imageScale(.small)
}
}
}
}.searchable(text: $searchText)
EDIT(2021/5/29):
I thinks there is a piece of important infomation I forgot to say
This view is a popover
I add searchable to my SwiftUI List
No, you added it to the navigation view. Move it up
NavigationView {
List(searchResults, id: \.self) { item in
NavigationLink {
LegendDetailView(item: item)
} label: {
HStack {
Text(item.name).padding(1)
Spacer()
Image(systemName: "chevron.right").imageScale(.small)
}
}
}.searchable(text: $searchText)
}
Text example
struct TestView: View {
#State private var letters = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta"]
#State private var searchText = ""
var body: some View {
let searchResults = searchText.isEmpty ? letters : letters.filter{$0.localizedCaseInsensitiveContains(searchText)}
NavigationView {
List(searchResults, id: \.self) { item in
NavigationLink {
Text(item)
} label: {
HStack {
Text(item).padding(1)
Spacer()
}
}
}.searchable(text: $searchText)
}
}
}

NavigationLink keeps aligning my text elements to center instead of leading SwiftUI

I have a CustomSearchBar view that looks like this
However, when I wrap it with NavigationLink, the placeholder text will be centered. And user inputs will be centered too.
How do I maintain the leading alignment while using NavigationLink?
My code structure looks like this:
enum Tab {
case social
}
struct MainAppView: View {
#State var selection: Tab = .social
var body: some View {
TabView(selection: $selection) {
ZStack{
CustomButton()
NavigationView { SocialView() }
}.tabItem{Image(systemName: "person.2")}.tag(Tab.social)
// other tabs....
}
struct SocialView: View {
// ...
var body: some View {
GeometryReader{ geometry in
VStack{
NavigationLink(destination: Text("test")) {
CustomSearchBar()
//...
}.navigationBarHidden(true)
.navigationBarTitle(Text(""))
}
}
}
}
struct CustomSearchBar: View {
var body: some View {
VStack{
HStack {
SearchBarSymbols(// some binding arguments)
CustomTextField(// some binding arguments)
CancelButton(// some binding arguments)
}
.padding(.vertical, 8.0)
.padding(.horizontal, 10.0)
.background(Color("SearchBarBackgroundColor"))
.clipShape(Capsule())
}
.padding(.horizontal)
}
}
struct CustomTextField: View {
var body: some View {
TextField("friend name", text: $searchText)
.frame(alignment: .leading)
.onTapGesture {
// some actions
}
.foregroundColor(Color("SearchBarSymbolColor"))
.accentColor(Color("SearchBarSymbolColor"))
.disableAutocorrection(true)
}
}
The issues with your code are:
Your navigation view contains the search field. This means that any new view that gets pushed will cover the search field.
Your search field is inside of the navigation link. There are conflicting interactions here as it effectively turns the field into a button, ie tapping the search field vs tapping the navigation link.
Solution:
Move the navigation view below the text field, so that the new view will appear without covering it. Then change the navigation link so that it is activated via a binding that gets triggered when the search field is editing:
struct SocialView: View {
#State private var text: String = ""
#State private var isActive: Bool = false
var body: some View {
GeometryReader{ geometry in
VStack {
CustomTextField(searchText: $text, isActive: $isActive)
.padding(.vertical, 8.0)
.padding(.horizontal, 10.0)
.background(Color("SearchBarBackgroundColor"))
.clipShape(Capsule())
NavigationView {
NavigationLink(isActive: $isActive, destination: { Text("test") }, label: { EmptyView() })
}
}
}
}
}
struct CustomTextField: View {
#Binding var searchText: String
#Binding var isActive: Bool
var body: some View {
TextField("friend name", text: $searchText) { editing in
self.isActive = editing
} onCommit: {
}
.frame(alignment: .leading)
.disableAutocorrection(true)
}
}

Display Multiple views in Picker

I am trying to display words in a Picker next to the selected option but the words are never displayed correctly. I want the user to be able to click the text to be able to open the Picker as well. I have tried putting the text outside the picker which leads to displaying correctly but the text is not clickable.
I am using Xcode 13 Beta 5 and iOS15.
struct ContentView: View {
#State var fruit = "Apple"
let fruits = ["Apple","Orange","Pear","Grape"]
var body: some View {
HStack {
Picker(selection: $fruit) {
Text("Picked:")
ForEach(fruits, id: \.self){ fruit in
Text(fruit)
}
} label: {
Text("")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Picture for Picker with text outside of Picker
Tweaks made to fit what I asked are shown below. Any thing put inside label is now clickable.
Menu {
Picker(selection: $fruit, label: EmptyView()) {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
}
} label: {
HStack {
Image(systemName: "hand.thumbsup.fill")
Text("Picked: \(fruit)")
}
}
Use a Menu with a inline Picker as its content:
Menu {
Picker(selection: $fruit, label: EmptyView()) {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
}
.labelsHidden()
.pickerStyle(InlinePickerStyle())
} label: {
HStack {
Text("Picked:")
Text(fruit)
}
}

Swiftui navigationLink macOS default/selected state

I build a macOS app in swiftui
i try to create a listview where the first item is preselected. i tried it with the 'selected' state of the navigationLink but it didn't work.
Im pretty much clueless and hope you guys can help me.
The code for creating this list view looks like this.
//personList
struct PersonList: View {
var body: some View {
NavigationView
{
List(personData) { person in
NavigationLink(destination: PersonDetail(person: person))
{
PersonRow(person: person)
}
}.frame(minWidth: 300, maxWidth: 300)
}
}
}
(Other views at the bottom)
This is the normal View when i open the app.
When i click on an item its open like this. Thats the state i want as default opening state when i render this view.
The Code for this view looks like this:
//PersonRow
struct PersonRow: View {
//variables definied
var person: Person
var body: some View {
HStack
{
person.image.resizable().frame(width:50, height:50)
.cornerRadius(25)
.padding(5)
VStack (alignment: .leading)
{
Text(person.firstName + " " + person.lastName)
.fontWeight(.bold)
.padding(5)
Text(person.nickname)
.padding(5)
}
Spacer()
}
}
}
//personDetail
struct PersonDetail: View {
var person : Person
var body: some View {
VStack
{
HStack
{
VStack
{
CircleImage(image: person.image)
Text(person.firstName + " " + person.lastName)
.font(.title)
Text("Turtle Rock")
.font(.subheadline)
}
Spacer()
Text("Subtitle")
.font(.subheadline)
}
Spacer()
}
.padding()
}
}
Thanks in advance!
working example. See how selection is initialized
import SwiftUI
struct Detail: View {
let i: Int
var body: some View {
Text("\(self.i)").font(.system(size: 150)).frame(maxWidth: .infinity)
}
}
struct ContentView: View {
#State var selection: Int?
var body: some View {
HStack(alignment: .top) {
NavigationView {
List {
ForEach(0 ..< 10) { (i) in
NavigationLink(destination: Detail(i: i), tag: i, selection: self.$selection) {
VStack {
Text("Row \(i)")
Divider()
}
}
}.onAppear {
if self.selection != nil {
self.selection = 0
}
}
}.frame(width: 100)
}
}.background(Color.init(NSColor.controlBackgroundColor))
}
}
screenshot
You can define a binding to the selected row and used a List reading this selection. You then initialise the selection to the first person in your person array.
Note that on macOS you do not use NavigationLink, instead you conditionally show the detail view with an if statement inside your NavigationView.
If person is not Identifiable you should add an id: \.self in the loop. This ressembles to:
struct PersonList: View {
#Binding var selectedPerson: Person?
var body: some View {
List(persons, id: \.self, selection: $selectedPerson) { person in // persons is an array of persons
PersonRow(person: person).tag(person)
}
}
}
Then in your main window:
struct ContentView: View {
// First cell will be highlighted and selected
#State private var selectedPerson: Person? = person[0]
var body: some View {
NavigationView {
PersonList(selectedPerson: $selectedPerson)
if selectedPerson != nil {
PersonDetail(person: person!)
}
}
}
}
Your struct person should be Hashable in order to be tagged in the list. If your type is simple enough, adding Hashable conformance should be sufficient:
struct Person: Hashable {
var name: String
// ...
}
There is a nice tutorial using the same principle here if you want a more complete example.
Thanks to this discussion, as a MacOS Beginner, I managed a very basic NavigationView with a list containing two NavigationLinks to choose between two views. I made it very basic to better understand. It might help other beginners.
At start up it will be the first view that will be displayed.
Just modify in ContentView.swift, self.selection = 0 by self.selection = 1 to start with the second view.
FirstView.swift
import SwiftUI
struct FirstView: View {
var body: some View {
Text("(1) Hello, I am the first view")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct FirstView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}
SecondView.swift
import SwiftUI
struct SecondView: View {
var body: some View {
Text("(2) Hello, I am the second View")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct SecondView_Previews: PreviewProvider {
static var previews: some View {
SecondView()
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#State var selection: Int?
var body: some View {
HStack() {
NavigationView {
List () {
NavigationLink(destination: FirstView(), tag: 0, selection: self.$selection) {
Text("Click Me To Display The First View")
} // End Navigation Link
NavigationLink(destination: SecondView(), tag: 1, selection: self.$selection) {
Text("Click Me To Display The Second View")
} // End Navigation Link
} // End list
.frame(minWidth: 350, maxWidth: 350)
.onAppear {
self.selection = 0
}
} // End NavigationView
.listStyle(SidebarListStyle())
.frame(maxWidth: .infinity, maxHeight: .infinity)
} // End HStack
} // End some View
} // End ContentView
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Result:
import SwiftUI
struct User: Identifiable {
let id: Int
let name: String
}
struct ContentView: View {
#State private var users: [User] = (1...10).map { User(id: $0, name: "user \($0)")}
#State private var selection: User.ID?
var body: some View {
NavigationView {
List(users) { user in
NavigationLink(tag: user.id, selection: $selection) {
Text("\(user.name)'s DetailView")
} label: {
Text(user.name)
}
}
Text("Select one")
}
.onAppear {
if let selection = users.first?.ID {
self.selection = selection
}
}
}
}
You can use make the default selection using onAppear (see above).