swiftUI list selection lead to textfield - list

As I have constructed a list of country codes and I am able to tap on the list items. Here i want to show the tapped or selected country code inside my textfield which means, when I click on a country code it should show inside the textfield. I am new to swiftUI, it would be great if someone helped me to get this.
enter image description here
state variables given as:
#State private var text = ""
#State private var selection: String!
My textfield code goes here:
HStack {
TextField("Country code", text: $text).padding(.leading)
.frame(width: 385, height: 50)
.border(.gray)
.padding(.leading, 25)
.overlay(
Button(action: {
withAnimation {
showCodes.toggle()
}
}, label: {
Image(systemName: "chevron.down")
.foregroundColor(.gray)
.padding(.leading, 320)
.position(x: 215, y: 25)
})
)
.position(x: 195, y: 40)
.padding(.top, -570)
}
List code goes here :
if showCodes == true {
List (selection: $selection) {
ForEach(datas.users, id: \.dial_code) { user in
HStack{
Text(user.name)
.font(.callout)
.foregroundColor(Color.black)
Spacer()
Text(user.dial_code)
.font(.callout)
.foregroundColor(Color.blue)
}
}
}
.listRowInsets(EdgeInsets())
.frame(maxWidth: 400, maxHeight: .infinity, alignment: .leading)
.padding(.top, -585)
}

You can have it like:
ForEach(datas.users, id: \.dial_code) { user in
HStack {
Text(user.name)
.font(.callout)
.foregroundColor(Color.black)
Spacer()
Text(user.dial_code)
.font(.callout)
.foregroundColor(Color.blue)
}
.onTapGesture { text = user.dial_code }
}
I also suggest extracting the HStack along with it's content in a separate func like:
private func getCountryCodeCell(for user: User) -> some View {
HStack {
Text(user.name)
.font(.callout)
.foregroundColor(Color.black)
Spacer()
Text(user.dial_code)
.font(.callout)
.foregroundColor(Color.blue)
}
}

Related

Keep VStack in center of the screen in SwiftUI

I'm new on SwiftUI and I don't know how to manage my views.
I have this code:
import SwiftUI
struct ContentView: View {
#State private var email: String = ""
#State private var passord: String = ""
var body: some View {
ZStack {
Image("corner")
.resizable()
.scaledToFill()
VStack {
VStack { //VStack1
TextField("Email", text: $email)
.frame(width: 300, height: 40)
.textFieldStyle(.roundedBorder)
.bold(true)
SecureField("Password", text: $passord)
.frame(width: 300, height: 40)
.textFieldStyle(.roundedBorder)
.bold(true)
Button {
//Do something
} label: {
Text("Forgot your password ?")
.underline()
.foregroundColor(.white)
}
}
VStack { // VStack2
Text("Not registered ?")
.font(.title2)
.foregroundColor(.white)
Button("Sign up") {}
.frame(width: 200, height: 37)
.foregroundColor(.white)
.background(Color(.orange))
.cornerRadius(20)
}
}
}
}
}
I want to place the VStack2 in the bottom of the screen and keep the VStack1 on the center of the screen.
How I can do that. I've try to search but I don't find the solution on StackOverflow.
I tried to play with Spacer() and padding() but I have not a good result.
Screen
A simple way to do this would be to add VStack1 to the ZStack which will place it in the centre of the screen. Then add wrap VStack2 in another VStack with a Spacer to push it to the bottom of the screen, e.g.
ZStack {
Image("corner")
VStack { //VStack1
// etc
}
VStack {
Spacer()
VStack { // VStack2
// etc
}
}
}
Simple put
HStack {
Spacer()
VStack {
Text("Centered")
}
Spacer()
}
import SwiftUI
struct ContentView: View {
#State private var email: String = ""
#State private var passord: String = ""
var body: some View {
VStack {
Spacer()
middleView()
Spacer()
bottomView()
.paddind(.bottom, 12)
}
.ignoresSafeArea()
.background {
Image("corner")
.resizable()
.scaledToFill()
}
}
func middleView() -> some View {
VStack(spacing: 20) {
TextField("Email", text: $email)
.frame(width: 300, height: 40)
.textFieldStyle(.roundedBorder)
.bold(true)
SecureField("Password", text: $passord)
.frame(width: 300, height: 40)
.textFieldStyle(.roundedBorder)
.bold(true)
Button {
//Do something
} label: {
Text("Forgot your password ?")
.underline()
.foregroundColor(.white)
}
}
}
func bottomView() -> some View() {
VStack {
Text("Not registered ?")
.font(.title2)
.foregroundColor(.white)
Button("Sign up") {}
.frame(width: 200, height: 37)
.foregroundColor(.white)
.background(Color(.orange))
.cornerRadius(20)
}
}
}

Limit the number of dots in Page Tab View Swiftui

I am trying to use the PageTabview option to allow a user to move through a series of pages whose data is coming from a JSON file. I want to be able to limit the number of visible dots to 5 or 6 even if there are many values in the field. What I don't want is to have 25 dots if there are twenty-five values in the field. How would I do that? I want to be able to show indicator like an arrow that tells the user there is more to come...Thank you.
My code is below:
struct TopicsExperienceCards: View {
#Binding var closeExperience: Bool
let etype: EItype
var body: some View {
//start of content of zstack layout
ZStack {
VStack(spacing: 20) {
HStack{
Rectangle()
.fill(Color.white)
.frame(width: 300, height: 1, alignment: .center)
Spacer()
Button(action: {
closeExperience = false })
{
Image(systemName:"xmark")
.foregroundColor(Color(etype.accentcolor))
.padding()
}
} //HSTACK
TabView {
ForEach(etype.experience,id: \.self) { item in
// Display the content of a card //
VStack (alignment:.center, spacing:0){
Text(item)
.padding()
.frame(width:300, height:300, alignment:.center)
Divider()
Spacer()
Text("Room for an image")
Spacer()
Spacer()
}//VSTACK
//End of display of content of the card //
} //: FOREACH
} //: TABVIEW
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.onAppear {
setupAppearance() }
} //VSTACK
} //: ZStack
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.white)
.overlay(
RoundedRectangle(cornerRadius: 16)
.strokeBorder()
.foregroundColor(Color(etype.accentcolor)))
.cornerRadius(16.0)
.padding()
}
}
struct TopicsExperienceCards_Previews: PreviewProvider {
static let etypes: [EItype] = Bundle.main.decode("eibasestructure.json")
static var previews: some View {
TopicsExperienceCards(closeExperience:.constant(true),etype:etypes[1])
}
}
enter image description here
System dots view is limited to around 10 dots, maybe depending on the device. You can't change this value.
Instead of that you can hide system one, and create your own view with dots. As an example you can follow this article, so at the end you'll have something like this:
#State var currentIndex = 0
var body: some View {
//start of content of zstack layout
ZStack {
printUI(currentIndex)
VStack(spacing: 20) {
HStack{
Rectangle()
.fill(Color.white)
.frame(width: 300, height: 1, alignment: .center)
Spacer()
Button(action: {
closeExperience = false })
{
Image(systemName:"xmark")
.foregroundColor(Color.red)
.padding()
}
} //HSTACK
TabView(selection: $currentIndex.animation()) {
ForEach(etype.experience.indices,id: \.self) { i in
let item = etype.experience[i]
// Display the content of a card //
VStack (alignment:.center, spacing:0){
Text(item)
.padding()
.frame(width:300, height:300, alignment:.center)
Divider()
Spacer()
Text("Room for an image")
Spacer()
Spacer()
}//VSTACK
//End of display of content of the card //
} //: FOREACH
} //: TABVIEW
.tabViewStyle(PageTabViewStyle())
.onAppear {
setupAppearance()
}
Fancy3DotsIndexView(numberOfPages: etype.experience.count, currentIndex: currentIndex)
.padding()
.background(Color.green)
.clipShape(Capsule())
} //VSTACK
} //: ZStack
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.white)
.overlay(
RoundedRectangle(cornerRadius: 16)
.strokeBorder()
.foregroundColor(Color.red)
)
.cornerRadius(16.0)
.padding()
}
Result:

How to change size of field in TextField?

I need to change the size (actually just the height) of the TextField. Using .padding() increases just the area around the TextField, but not the field itself. I've tried some possible solutions:
Change the font size: This approach works, but I don't want to have a TextField with a too large text in itself.
Using custom modifier doesn't work. I get the message Inheritance from non-protocol type 'TextFieldStyle'. Seems to not working in Swift 5 anymore?
My current solution is:
#State private var searchText: String = ""
var body: some View {
HStack{
Image(systemName: "magnifyingglass")
.font(.system(size: 20, weight: .bold))
ZStack(alignment: .leading){
if searchText.isEmpty { Text("search") }
TextField("", text: $searchText,
onEditingChanged: { focused in
if focused {
// do something...
}
},
onCommit: {
// do something...
})
.keyboardType(.alphabet)
.disableAutocorrection(true)
.frame(height: 50)
}
.textFieldStyle(PlainTextFieldStyle())
}
.frame(height: 30)
.frame(maxWidth: .infinity)
.foregroundColor(.white)
.accentColor(.white)
.padding()
.background(
Color("Color-2")
.cornerRadius(20)
.shadow(color: Color.black.opacity(0.3), radius: 5, x: 5, y: 5)
)
.contentShape(Rectangle())
}}
Only the red area is "clickable"
import SwiftUI
struct ContentView: View {
#State var stringOfText: String = ""
var body: some View {
TextField("", text: $stringOfText)
.font(Font.system(size: 50))
.background(Color.yellow)
.foregroundColor(Color.clear)
.cornerRadius(10)
.overlay(Text(stringOfText), alignment: .leading)
.padding()
.shadow(radius: 10)
}
}

Edit mode in a List

Updated with working code.
I've implemented a List in my app with Edit mode, so I can move the rows by dragging a row handle. That works fine, but doesn't look too good, since the move icon is placed under the content of the row (see screen dump). And that is because Edit mode makes room for a delete button.
Is there a way to hide elements in the row when you're in Edit mode?
The code for the View is:
import SwiftUI
import Combine
import DateHelper
struct EggList: View {
#EnvironmentObject var egg : Egg
#State private var eggs = Egg.all()
#State private var editMode: EditMode = .inactive
var body: some View {
NavigationView {
List {
Image("Pantanal")
.resizable()
.frame(height: 250)
ForEach(eggs) { eggItem in
NavigationLink(destination: EggDayList(eggItem: eggItem)) {
CellRow(eggItem: eggItem)
.environment(\.editMode, self.$editMode)
}
}
.onDelete(perform: delete)
.onMove(perform: move)
}
.navigationBarTitle(Text("Eggs"), displayMode: .inline)
.navigationBarItems(leading: EditButton(), trailing: NavigationLink(destination: Settings()){
Text("Add Egg")})
.environment(\.editMode, self.$editMode)
}
}
func delete(at offsets: IndexSet) {
eggs.remove(atOffsets: offsets)
}
func move(from source: IndexSet, to destination: Int) {
eggs.move(fromOffsets: source, toOffset: destination)
}
}
struct CellRow: View {
let eggItem: Egg
#Environment(\.editMode) private var editMode
var body: some View {
HStack(spacing: 8) {
Image(eggItem.species)
.resizable()
.frame(width: 48, height: 48)
.clipShape(Circle())
VStack(alignment: .leading, spacing: 0) {
Text("\(eggItem.species)")
.font(.footnote)
.lineLimit(1)
.padding(.top, -4)
Text("id-"+String(eggItem.eggNumber))
.font(.footnote)
.lineLimit(1)
.padding(0)
Text("\(eggItem.layDate.string(with: "dd-MM-yy"))")
.font(.footnote)
.lineLimit(1)
.padding(.bottom, -7)
}.frame(width: 90, alignment: .leading)
VStack(spacing: 2) {
Text("days")
.font(.footnote)
.padding(.top, 12)
Image(systemName: "\(eggItem.diffToday)"+".circle")
.resizable()
.frame(width: 40, height: 30)
.padding(.bottom, 12)
.foregroundColor(.red)
}.frame(width: 50, alignment: .leading)
VStack(spacing: 0) {
Text("prediction")
.font(.footnote)
.padding(.top, 14)
Text(formatVar1(getal: eggItem.calcWeights[eggItem.daysToPip-1].prediction)+"%")
.font(.title)
.padding(.bottom, 12)
}.frame(width: 80, alignment: .leading)
if !(self.editMode?.wrappedValue.isEditing ?? false) {
VStack(alignment: .leading, spacing: 0) {
Text("INC")
.font(.footnote)
.lineLimit(1)
.padding(.top, -4)
Text("37.3")
.font(.footnote)
.lineLimit(1)
.padding(0)
Text("30%")
.font(.footnote)
.lineLimit(1)
.padding(.bottom, -7)
}
.frame(width: 30, alignment: .leading)
}
Spacer()
VStack(alignment: .trailing, spacing: 0) {
Image(systemName: "info.circle")
.resizable()
.frame(width: 22, height: 22)
.foregroundColor(.accentColor)
.onTapGesture(count: 1) {
print("action")
}
}
}
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
.frame(height: 46, alignment: .leading)
.padding(0)
}
}
Your problem is, that the editMode variable does not change when you press the EditButton. No matter in what state, in your CellRow the editMode variable always returns .inactive. I don't know why, it could be a bug.
I had the same problem, but I found this question here: SwiftUI - How do I make edit rows in a list?, which uses a workaround by passing the editMode environment value into a private #State variable, which seems to work perfect.
So here is what you need to do:
Add #State private var editMode: EditMode = .inactive to your EggList view.
This creates a state variable that from now on holds the editing mode.
Add .environment(\.editMode, self.$editMode) somewhere after the .navigationBarItems(...).
This sets the environment variable editMode in EggList to a binding of the state variable above.
Add .environment(\.editMode, self.$editMode) directly after the CellRow(...) initializer.
This inserts the state variable editMode into the environment of CellRow, where it can be accessed via #Environment(\.editMode).
Now you can just wrap one of your elements in an if-statement to hide it when in editing mode:
if !self.editMode?.wrappedValue.isEditing ?? true {
VStack(alignment: .leading, spacing: 0) {
Text("INC")
.font(.footnote)
.lineLimit(1)
.padding(.top, -4)
Text("37.3")
.font(.footnote)
.lineLimit(1)
.padding(0)
Text("30%")
.font(.footnote)
.lineLimit(1)
.padding(.bottom, -7)
}
.frame(width: 30, alignment: .leading)
}
or, if you prefer, continue using the isHidden extension from LuLuGaGa:
.isHidden(self.editMode?.wrappedValue.isEditing ?? false)
Add a little extension to View:
extension View {
func isHidden(_ hidden: Bool) -> some View {
if hidden {
return AnyView(self.hidden())
} else {
return AnyView(self)
}
}
}
Next add #Enviroment to your CellRow:
#Environment(\.editMode) var editMode: Binding<EditMode>?
You will be able to add a modifier:
.isHidden(editMode?.wrappedValue.isEditing ?? false)
to one of the stacks that you think is the least important.

SwiftUI Button tap only on text portion

The background area of my button is not detecting user interaction. Only way to interact with said button is to tap on the Text/ Label area of the button. How to make entire Button tappable?
struct ScheduleEditorButtonSwiftUIView: View {
#Binding var buttonTagForAction : ScheduleButtonType
#Binding var buttonTitle : String
#Binding var buttonBackgroundColor : Color
let buttonCornerRadius = CGFloat(12)
var body: some View {
Button(buttonTitle) {
buttonActionForTag(self.buttonTagForAction)
}.frame(minWidth: (UIScreen.main.bounds.size.width / 2) - 25, maxWidth: .infinity, minHeight: 44)
.buttonStyle(DefaultButtonStyle())
.lineLimit(2)
.multilineTextAlignment(.center)
.font(Font.subheadline.weight(.bold))
.foregroundColor(Color.white)
.border(Color("AppHighlightedColour"), width: 2)
.background(buttonBackgroundColor).opacity(0.8)
.tag(self.buttonTagForAction)
.padding([.leading,.trailing], 5)
.cornerRadius(buttonCornerRadius)
}
}
The proper solution is to use the .contentShape() API.
Button(action: action) {
HStack {
Spacer()
Text("My button")
Spacer()
}
}
.contentShape(Rectangle())
You can change the provided shape to match the shape of your button; if your button is a RoundedRectangle, you can provide that instead.
I think this is a better solution, add the .frame values to the Text() and the button will cover the whole area 😉
Button(action: {
//code
}) {
Text("Click Me")
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .center)
.foregroundColor(Color.white)
.background(Color.accentColor)
.cornerRadius(7)
}
You can define content Shape for hit testing by adding modifier: contentShape(_:eoFill:)
And important thing is you have to apply inside the content of Button.
Button(action: {}) {
Text("Select file")
.frame(width: 300)
.padding(100.0)
.foregroundColor(Color.black)
.contentShape(Rectangle()) // Add this line
}
.background(Color.green)
.cornerRadius(4)
.buttonStyle(PlainButtonStyle())
Another
Button(action: {}) {
VStack {
Text("Select file")
.frame(width: 100)
Text("Select file")
.frame(width: 200)
}
.contentShape(Rectangle()) // Add this inside Button.
}
.background(Color.green)
.cornerRadius(4)
.buttonStyle(PlainButtonStyle())
This fixes the issue on my end:
var body: some View {
GeometryReader { geometry in
Button(action: {
// Action
}) {
Text("Button Title")
.frame(
minWidth: (geometry.size.width / 2) - 25,
maxWidth: .infinity, minHeight: 44
)
.font(Font.subheadline.weight(.bold))
.background(Color.yellow).opacity(0.8)
.foregroundColor(Color.white)
.cornerRadius(12)
}
.lineLimit(2)
.multilineTextAlignment(.center)
.padding([.leading,.trailing], 5)
}
}
Is there a reason why you are using UIScreen instead of GeometryReader?
Short Answer
Make sure the Text (or button content) spans the length of the touch area, AND use .contentShape(Rectangle()).
Button(action:{}) {
HStack {
Text("Hello")
Spacer()
}
.contentShape(Rectangle())
}
Long Answer
There are two parts:
The content (ex. Text) of the Button needs to be stretched
The content needs to be considered for hit testing
To stretch the content (ex. Text):
// Solution 1 for stretching content
HStack {
Text("Hello")
Spacer()
}
// Solution 2 for stretching content
Text("Hello")
.frame(maxWidth: .infinity, alignment: .leading)
// Alternatively, you could specify a specific frame for the button.
To consider content for hit testing use .contentShape(Rectangle()):
// Solution 1
Button(action:{}) {
HStack {
Text("Hello")
Spacer()
}
.contentShape(Rectangle())
}
// Solution 2
Button(action:{}) {
Text("Hello")
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
}
You might be doing this:
Button { /*to do something on button click*/}
label: { Text("button text").foregroundColor(Color.white)}
.frame(width: 45, height: 45, alignment: .center)
.background(Color.black)
Solution:
Button(action: {/*to do something on button click*/ })
{
HStack {
Spacer()
Text("Buttton Text")
Spacer() } }
.frame(width: 45, height: 45, alignment: .center)
.foregroundColor(Color.white)
.background(Color.black).contentShape(Rectangle())
A bit late to the answer, but I found two ways to do this —
Option 1: Using Geometry Reader
Button(action: {
}) {
GeometryReader { geometryProxy in
Text("Button Title")
.font(Font.custom("SFProDisplay-Semibold", size: 19))
.foregroundColor(Color.white)
.frame(width: geometryProxy.size.width - 20 * 2) // horizontal margin
.padding([.top, .bottom], 10) // vertical padding
.background(Color.yellow)
.cornerRadius(6)
}
}
Option 2: Using HStack with Spacers
HStack {
Spacer(minLength: 20) // horizontal margin
Button(action: {
}) {
Text("Hello World")
.font(Font.custom("SFProDisplay-Semibold", size: 19))
.frame(maxWidth:.infinity)
.padding([.top, .bottom], 10) // vertical padding
.background(Color.yellow)
.foregroundColor(Color.white)
.cornerRadius(6)
}
Spacer(minLength: 20)
}.frame(maxWidth:.infinity)
My thought process here is that although option 1 is more succinct, I would choose option 2 since it's less coupled to its parent's size (through GeometryReader) and more in line of how I think SwiftUI is meant to use HStack, VStack, etc.
I was working with buttons and texts that need user interaction when I faced this same issue. After looking and testing many answers (including some from this post) I ended up making it works in the following way:
For buttons:
/* WITH IMAGE */
Button {
print("TAppeD")
} label: {
Image(systemName: "plus")
.frame(width: 40, height: 40)
}
/* WITH TEXT */
Button {
print("TAppeD")
} label: {
Text("My button")
.frame(height: 80)
}
For Texts:
Text("PP")
.frame(width: 40, height: 40)
.contentShape(Rectangle())
.onTapGesture {
print("TAppeD")
}
In the case of the texts, I only need the .contentShape(Rectangle()) modifier when the Text doesn't have a .background in order to make the entire Text frame responsive to tap gesture, while with buttons I use my Text or Image view with a frame and neither a .background nor a .contentShape is needed.
Image of the following code in preview (I'm not allowed to include pictures yet )
import SwiftUI
struct ContentView: View {
#State var tapped: Bool = true
var body: some View {
VStack {
RoundedRectangle(cornerRadius: 19)
.frame(width: 40, height: 40)
.foregroundColor(tapped ? .red : .green)
Spacer()
HStack (spacing: 0) {
Text("PP")
.frame(width: 40, height: 40)
.contentShape(Rectangle())
.onTapGesture {
tapped.toggle()
}
Button {
print("TAppeD")
tapped.toggle()
} label: {
Image(systemName: "plus")
.frame(width: 40, height: 40)
}
.background(Color.red)
Button {
print("TAppeD")
tapped.toggle()
} label: {
Text("My button")
.frame(height: 80)
}
.background(Color.yellow)
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
this way makes the button area expand properly
but if the color is .clear, it dosen't work🤷‍♂️
Button(action: {
doSomething()
}, label: {
ZStack {
Color(.white)
Text("some texts")
}
})
When I used HStack then it worked for button whole width that's fine, But I was facing issue with whole button height tap not working at corners and I fixed it in below code:
Button(action:{
print("Tapped Button")
}) {
VStack {
//Vertical whole area covered
Text("")
Spacer()
HStack {
//Horizontal whole area covered
Text("")
Spacer()
}
}
}
If your app needs to support both iOS/iPadOS and macOS, you may want to reference my code!
Xcode 14.1 / iOS 14.1 / macOS 13.0 / 12-09-2022
Button(action: {
print("Saved to CoreData")
}) {
Text("Submit")
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 60, alignment: .center)
.foregroundColor(Color.white)
#if !os(macOS)
.background(Color.accentColor)
#endif
}
#if os(macOS)
.background(Color.accentColor)
#endif
.cornerRadius(7)
Easier work around is to add .frame(maxWidth: .infinity) modifier.
and wrap your button inside a ContainerView. you can always change the size of the button where it's being used.
Button(action: tapped) {
HStack {
if let icon = icon {
icon
}
Text(title)
}
.frame(maxWidth: .infinity) // This one
}