Swiftui Focus Field in TabView - swiftui

I'm using TabView to display a scrollable tab list of players to update. I am trying to focus the newScoreEntry field upon scrolling to the next TabView. I am having trouble figuring out how to focus the right field based upon my selectedTab. I think I have to somehow define my focusfield as an array of fields or something?
struct ScoreRoundView: View {
#StateObject var game: Game
#State var newScoreEntry: [String] = Array(repeating: "", count: 50)
#State var selectedTab = 0
#FocusState private var focusScore: Bool
var body: some View {
TabView(selection: $selectedTab) {
ForEach(Array(zip(game.playersArray.indices, game.playersArray)), id: \.1) { i, player in
TextField("Round Score", text: $newScoreEntry[i])
.focused($focusScore)
}.tag(i)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
.onChange(of: selectedTab, perform: { value in
focusScore = true
})
}
}

I figured out what i was looking for, so i thought i would post the solution. I had to use focusable field with an id associated with it.
enum Focusable: Hashable {
case tabView(id: Int)
}
#FocusState var focusedField: Focusable?
#StateObject var game: Game
#State var newScoreEntry: [String] = Array(repeating: "", count: 50)
#State var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
ForEach(Array(zip(game.playersArray.indices, game.playersArray)), id: \.1) { i, player in
TextField("Round Score", text: $newScoreEntry[i])
.focused($focusedField, equals: .tabView(id: i))
}.tag(i)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
.onChange(of: selectedTab, perform: { value in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.focusedField = .tabView(id: value)
}
})

Related

TextField #State property value = ""

I'm studying SwiftUI and I'm having a hard time dealing with TextField.
When I do onCommit, I tried to put "" in the #State Property value, but there was no response.
What I'm trying to do is click the'retrun' key in TextField and I want to show you the gap where the text created disappeared.
struct TodoTextFieldView: View {
#EnvironmentObject var listViewModel: ListViewModel
#State var textFieldText = ""
var title: String = "here typing line"
var body: some View {
VStack{
HStack(spacing: 5){
Text("๐Ÿ”ฅ")
.font(.title)
TextField("\(title)",
text: $textFieldText,
onCommit:{ addItem()
//
})
.disableAutocorrection(true)
.underline()
.font(.headline)
}.padding(.leading, 40)
}.padding(16)
}
func addItem() {
listViewModel.addItem(title: textFieldText)
textFieldText = "" // This is where I want to do it.
print(textFieldText)
}
This is a parent view.
struct ContentView: View {
#EnvironmentObject var listViewModel: ListViewModel
var body: some View {
VStack {
TodayView()
Spacer()
TodoTextFieldView()
Spacer()
/// List view
List{
ForEach(listViewModel.items) { item in
ListView(item: item)
.onTapGesture {
withAnimation(.linear){
listViewModel.updateItem(item: item)
}
}
}
.onDelete(perform: listViewModel.deleteItem) // Delete
.onMove(perform: listViewModel.moveItem) // Edit
}.listStyle(PlainListStyle())
}.navigationBarItems(trailing: EditButton()) // Edit Button
.padding()
}
}
#State var textFieldText = ""
The value was directly added to the state property. But there is no reaction.
textFieldText = ""
Emptying the binding property of TextField inside the Task block works for me.
func addItem() {
listViewModel.addItem(title: textFieldText)
Task {
textFieldText = ""
}
}

SwiftUI: NavigationView focus on last selected NavigationLink

maybe a very simple problem:
I use a navigation with a long list of entries. If the user returns from the navigationLink the list starts on the first item. How can I set the focus on the last selected navigationLink so the user don't need to scroll from the beginning again.
My app is for blind people so the scrolling from above isn't an easy thing.
ยดยดยด
struct CategoryDetailView: View {
#EnvironmentObject var blindzeln: BLINDzeln
#AppStorage ("version") var version: Int = 0
#State var shouldRefresh: Bool = false
#State private var searchText = ""
let categoryTitle: String
let catID: Int
var body: some View {
VStack{
List {
ForEach(blindzeln.results.filter { searchText.isEmpty || ($0.title.localizedCaseInsensitiveContains(searchText) || $0.textBody.localizedCaseInsensitiveContains(searchText)) }, id: \.entryID){ item in
NavigationLink(destination: ItemDetailViewStandard(item: item, isFavorite: false, catID: catID)) {DisplayEntryView(item: item, catID: catID)}.listRowSeparatorTint(.primary).listRowSeparator(.hidden)
}
}
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "") {}
.navigationTitle(categoryTitle)
.navigationBarTitleDisplayMode(.inline)
.listStyle(.inset)
}
.task(){
await blindzeln.decodeCategoryData(showCategory: categoryTitle)
}
.onAppear(){
blindzeln.resetData()
}
}
}
ยดยดยด
you could try this approach, using the List with selection, such
as in this example code. It does not scroll back to the beginning of the list
after selecting a destination.
struct ContentView: View {
#State private var selections = Set<Thing>()
#State var things: [Thing] = []
var body: some View {
NavigationStack {
List(things, selection: $selections){ thing in
NavigationLink(destination: Text("destination-\(thing.val)")) {
Text("item-\(thing.val)")
}
}
}
.onAppear {
(0..<111).forEach{things.append(Thing(val: $0))}
}
}
}
EDIT-1:
Since there are so many elements missing from you code, I can only guess
and suggest something like this:
struct CategoryDetailView: View {
#EnvironmentObject var blindzeln: BLINDzeln
#AppStorage ("version") var version: Int = 0
#State var shouldRefresh: Bool = false
#State private var searchText = ""
#State private var selections = Set<Thing>() // <-- same type as item in the List
let categoryTitle: String
let catID: Int
var body: some View {
VStack {
// -- here
List(blindzeln.results.filter { searchText.isEmpty || ($0.title.localizedCaseInsensitiveContains(searchText) || $0.textBody.localizedCaseInsensitiveContains(searchText)) },
id: \.entryID,
selection: $selections){ item in
NavigationLink(destination: ItemDetailViewStandard(item: item, isFavorite: false, catID: catID)) {
DisplayEntryView(item: item, catID: catID)
}
.listRowSeparatorTint(.primary).listRowSeparator(.hidden)
}
}
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "") {}
.navigationTitle(categoryTitle)
.navigationBarTitleDisplayMode(.inline)
.listStyle(.inset)
.task{
await blindzeln.decodeCategoryData(showCategory: categoryTitle)
}
.onAppear{
blindzeln.resetData()
}
}
}

SwiftUI - Subtotal TextField entries across multiple views

I have multiple views created by a ForEACH. Each View has a textfield where a user can enter a number. I would like to subtotal each entry in each view. In other words subtotal the binding in each view.
Is my approach wrong?
ForEach(someArray.allCases, id: \.id) { item in
CustomeRowView(name: item.rawValue)
}
struct CustomeRowView: View {
var name: String
#State private var amount: String = ""
var body: some View {
VStack {
HStack {
Label(name, systemImage: image)
VStack {
TextField("Amount", text: $amount)
.frame(width: UIScreen.main.bounds.width / 7)
}
}
}
}
}
Any help would be greatly appreciated.
there are many ways to achieve what you ask. I present here a very
simple approach, using an ObservableObject to keep the info in one place.
It has a function to add to the info dictionary fruits.
A #StateObject is created in ContentView to keep one single source of truth.
It is passed to the CustomeRowView view using #ObservedObject, and used to tally
the input of the TextField when the return key is pressed (.onSubmit).
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class FruitCake: ObservableObject {
#Published var fruits: [String : Int] = ["apples":0,"oranges":0,"bananas":0]
// adjust for you purpose
func add(to name: String, amount: Int) {
if let k = fruits.keys.first(where: {$0 == name}),
let sum = fruits[k] {
fruits[k] = sum + amount
}
}
}
struct ContentView: View {
#StateObject var fruitCake = FruitCake()
var body: some View {
VStack {
ForEach(Array(fruitCake.fruits.keys), id: \.self) { item in
CustomeRowView(name: item, fruitCake: fruitCake)
}
}
}
}
struct CustomeRowView: View {
let name: String
#ObservedObject var fruitCake: FruitCake
#State private var amount = 0
var body: some View {
HStack {
Label(name, systemImage: "info")
TextField("Amount", value: $amount, format: .number)
.frame(width: UIScreen.main.bounds.width / 7)
.border(.red)
.onSubmit {
fruitCake.add(to: name, amount: amount)
}
// subtotal
Text("\(fruitCake.fruits[name] ?? 0)")
}
}
}

Can't transfer variable from one watchos view to another view, Using swiftui

I am trying to get data from one view to another.
I can not figure out how to get values from the fourth view array into the Third view.
I am not using storyboards. I tried using #EnvironmentObject but can not make it work. New to coding. In xcode I am using watchos without app.
I tried to strip out most of the code and leave just the important stuff that can be tested. I used NavigationLink(destination: )to transfer between views.
enter code here
class viewFromEveryWhere: ObservableObject {
#Published var testname2: String = "testTTname"
}
struct secondView: View {
var body: some View {
Text("second view")
List(1..<7) {
Text("\($0)")
}
}
}
struct thirdView: View {
#EnvironmentObject var testname2: viewFromEveryWhere
#EnvironmentObject var testSixTestArray: viewFromEveryWhere
#State var sixTestArray:[String] = ["ww","GS","DW","CD","TS","JW",]
var body: some View {
List(sixTestArray, id:\.self) {
Text($0)
}
}
}
struct fourthView: View {
#StateObject var testname2 = viewFromEveryWhere()
#State private var name: String = ""
#State var testSixTestArray:[String] = []
func collectName () {
print("collectName triggered")
if testSixTestArray.count < 5 {
// testSixTestArray.append(name)
print(name)
print(testSixTestArray)
}
// .enviromentObject(testSixTestArray)
}
var body: some View {
VStack(alignment: . leading) {
Text("Type a name")
TextField("Enter your name", text: $name)
Text("Click to add, \(name)!")
// Button("click this if \(name) is correct") {}
Button(action:{
print("Button Tapped")
collectName()
print(testSixTestArray.count)
name = ""
}) {
Text("Add \(name) to list")
}
// .buttonStyle(GrowingButton1())
}
Text("forth view")
// testSixTestArray.append(name)
.environmentObject(testname2)
}
}
/*func presentTextInputControllerWithSuggestions(forLanguage suggestionsHandler:
((String)-> [Any]?)?,
allowedInputMode inputMode:
WKTextInputMode,
completion: #escaping ([Any]?) -> Void) {}
*/
struct ContentView: View {
#State var sixNameArray:[String] = ["","","","","","",]
#State var messageTextBox: String = "Start"
#State var button1: String = "Button 1"
#State var button2: String = "Button 2"
#State var button3: String = "Button 3"
var body: some View {
NavigationView {
VStack{
Text(messageTextBox)
.frame(width: 120, height: 15, alignment: .center)
.truncationMode(.tail)
.padding()
NavigationLink(destination: secondView(),
label:{
Text(button1)
})
.navigationBarTitle("Main Page")
NavigationLink(destination: thirdView(),
label:{
Text(button2)
})
NavigationLink(destination: fourthView(),
label:{
Text(button3)
})
}
}
}
}
enter code here

SwiftUI different actions on the same Custom Alert actions

I created a custom alert. I want the product to be added to the basket when the Ok button on Alert is clicked on the first screen. When the Ok button is pressed on the second screen, the purchase of the product is requested. I called the same alert on 2 pages and I want it to take different actions. I couldn't do that with #Escaping.
AlertView
struct AlertView: View {
#Binding var openShowAlert: Bool
#State var closeShowAlert: Bool = false
#State var openState: CGFloat = -UIScreen.main.bounds.height
#State var closeState: CGFloat = UIScreen.main.bounds.height
var title: String = ""
var message: String = ""
var okButtonText: String = ""
var cancelButtonText: String = ""
var body: some View {
VStack {
Text(title)
.michromaFont(size: 20)
.padding(.top)
Spacer()
Text(message)
.michromaFont(size: 18)
Spacer()
HStack {
Button(action: {
self.openShowAlert = false
openState = -UIScreen.main.bounds.height
closeState = UIScreen.main.bounds.height
}) {
Text(cancelButtonText)
.foregroundColor(.red)
}
Spacer()
Button(action: {}) {
Text(okButtonText)
}
}
.michromaFont(size: 18)
.padding([.horizontal, .bottom])
}
.neumorphisimBackground(width: 300, height: 200)
.offset(y: self.openShowAlert ? self.openState : self.closeState)
.animation(.easeInOut)
.onChange(of: self.openShowAlert, perform: { value in
if value {
self.openState = .zero
}
})
}
}
DetailView
On this screen, click Alert presentation to add the product to the cart.
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode
var device = UIDevice.current.userInterfaceIdiom
#State var width: CGFloat = 300
#State var height: CGFloat = 450
#Binding var text: String
#State var showAlert: Bool = false
var body: some View {
ZStack() {
......
AlertView(openShowAlert: self.$showAlert)
}
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
CartView Click I am providing an alert on this screen to purchase the product.
struct CartView: View {
#State var cartList = [1,2,3,4,5,6,7,8,9]
#Environment(\.presentationMode) var presentationMode
#State var showAlert: Bool = false
var body: some View {
ZStack(alignment: .top) {
.....
AlertView(openShowAlert: self.$showAlert)
}
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
How can I send two different actions in the same alert.
Hmm, I don't see why it shouldn't work with a closure. Have you tried passing over a closure like so?
struct AlertView: View {
...
var okButtonAction: () -> ()
var body: some View {
...
Button(action: okButtonAction) {
Text(okButtonText)
}
}
}
Usage
AlertView(openShowAlert: self.$showAlert) {
// Your custom code
}
Alternative Idea
You could work with Combine and create a publisher with a specific key to identify the sender screen. Then you can put your custom code inside .onReceive().