I am trying to clear the text field after a button click and the sendMessage function. I keep running into errors and I cant figure it out. anyone have any ideas? Ive tried simply making a Bool variable in the first struct and toggling the value of it after the sendMesssages function. That causes an error of self is immutable and I cant access this var in the CustomMessageField where I have to do TextField.text = "" to clear it
struct MessagesField: View {
var body: some View {
HStack{
CustomMessageField(placeholder: Text("Enter Your Message Here"), text: $message)
Button {
viewModel.sendMessages(withOtherUserUid: viewModeler.user.id ?? "", withText: message)
} label: {
Image(systemName: "paperplane.fill")
}
}
}
}
struct CustomMessageField: View{
var placeholder: Text
#Binding var text: String
var editingChanged: (Bool) -> () = {_ in}
var commit: () -> () = {}
var body: some View{
ZStack(alignment: .leading){
if text.isEmpty{
placeholder
.opacity(0.5)
}
TextField("", text: $text, onEditingChanged: editingChanged, onCommit: commit)
}
}
}
I think you are missing a #State var for the input. Once you have that you can easily set it to "" after sending:
struct MessagesField: View {
#State private var message: String = "" // declare here
var body: some View {
HStack{
CustomMessageField(placeholder: Text("Enter Your Message Here"), text: $message)
Button {
viewModel.sendMessages(withOtherUserUid: viewModeler.user.id ?? "", withText: message)
message = "" / reset here after send
} label: {
Image(systemName: "paperplane.fill")
}
}
}
}
Related
I have a simple notes app that uses a list on the left and a texteditor on the right. The list has the titles of the notes, and the texteditor its text. When the user changes the text on the note, the list gets sorted to display the most current note (by date) on top, as its first item.
Started with Ventura, if I'm working on a note other than the first one, then that note (the item in the list) jumps to the top when the text is changed, however, if the first item in the list not visible (I'm working on a note that is way down), then when I change the text the item, it jumps to the top, but you don't jump with it. You are now in this state where you have to scroll up to get to the top and reselect the first item.
I tried using DispatchQueue.main.async to force to reselect the item onchange, but regardless of what I try, it doesn't scroll to the top, even when the selected note id is the correct one.
I ran out of ideas or things to try. How can I go back to the first item once the text is changed?
Here's the code:
struct NoteItem: Codable, Hashable, Identifiable {
let id: Int
var text: String
var date = Date()
var dateText: String {
dateFormatter.dateFormat = "EEEE, MMM d yyyy, h:mm a"
return dateFormatter.string(from: date)
}
var tags: [String] = []
}
final class DataModel: ObservableObject {
#AppStorage("notes") public var notes: [NoteItem] = []
}
struct AllNotes: View {
#EnvironmentObject private var data: DataModel
#State var noteText: String = ""
#State var selectedNoteId: UUID?
var body: some View {
NavigationView {
List(data.notes) { note in
NavigationLink(
destination: NoteView(note: note),
tag: note.id,
selection: $selectedNoteId
) {
VStack(alignment: .leading) {
Text(note.text.components(separatedBy: NSCharacterSet.newlines).first!)
Text(note.dateText).font(.body).fontWeight(.light)
}
.padding(.vertical, 8)
}
}
.listStyle(InsetListStyle())
}
.navigationTitle("A title")
.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: {
data.notes.append(NoteItem(id: UUID(), text: "New Note", date: Date(), tags: []))
}) {
Image(systemName: "square.and.pencil")
}
}
}
.onAppear {
DispatchQueue.main.async {
selectedNoteId = data.notes.first?.id
}
}
.onChange(of: data.notes) { notes in
if selectedNoteId == nil || !notes.contains(where: { $0.id == selectedNoteId }) {
selectedNoteId = data.notes.first?.id
}
}
}
}
struct NoteView: View {
#EnvironmentObject private var data: DataModel
var note: NoteItem
#State var text: String = ""
var body: some View {
HStack {
VStack(alignment: .leading) {
TextEditor(text: $text).padding().font(.body)
.onChange(of: text, perform: { value in
guard let index = data.notes.firstIndex(of: note) else { return }
data.notes[index].text = value
data.sortList()
})
Spacer()
}
Spacer()
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
I'm trying to present a View in a sheet with a #Binding String variable that just shows/binds this variable in a TextField.
In my main ContentView I have an Array of Strings which I display with a ForEach looping over the indices of the Array, showing a Button each with the text of the looped-over-element.
The Buttons action is simple: set an #State "index"-variable to the pressed Buttons' Element-index and show the sheet.
Here is my ContentView:
struct ContentView: View {
#State var array = ["first", "second", "third"]
#State var showIndex = 0
#State var showSheet = false
var body: some View {
VStack {
ForEach (0 ..< array.count, id:\.self) { i in
Button("\(array[i])") {
showIndex = i
showSheet = true
}
}
// Text("\(showIndex)") // if I uncomment this line, it works!
}
.sheet(isPresented: $showSheet, content: {
SheetView(text: $array[showIndex])
})
.padding()
}
}
And here is the SheetView:
struct SheetView: View {
#Binding var text: String
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
TextField("text:", text: $text)
Button("dismiss") {
presentationMode.wrappedValue.dismiss()
}
}.padding()
}
}
The problem is, when I first open the app and press on the "second" Button, the sheet opens and displays "first" in the TextField. I can then dismiss the Sheet and press the "second" Button again with the same result.
If I then press the "third" or "first" Button everything works from then on. Pressing any Button results in the correct behaviour.
Preview
Interestingly, if I uncomment the line with the Text showing the showIndex-variable, it works from the first time on.
Is this a bug, or am I doing something wrong here?
You should use custom Binding, custom Struct for solving the issue, it is complex issue. See the Example:
struct ContentView: View {
#State private var array: [String] = ["first", "second", "third"]
#State private var customStruct: CustomStruct?
var body: some View {
VStack {
ForEach (array.indices, id:\.self) { index in
Button(action: { customStruct = CustomStruct(int: index) }, label: {
Text(array[index]).frame(width: 100)
})
}
}
.frame(width: 300, height: 300, alignment: .center)
.background(Color.gray.opacity(0.5))
.sheet(item: $customStruct, content: { item in SheetView(text: Binding.init(get: { () -> String in return array[item.int] },
set: { (newValue) in array[item.int] = newValue }) ) })
}
}
struct CustomStruct: Identifiable {
let id: UUID = UUID()
var int: Int
}
struct SheetView: View {
#Binding var text: String
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
TextField("text:", text: $text)
Button("dismiss") {
presentationMode.wrappedValue.dismiss()
}
}.padding()
}
}
I had this happen to me before. I believe it is a bug, in that until it is used in the UI, it doesn't seem to get set in the ForEach. I fixed it essentially in the same way you did, with a bit of subtlety. Use it in each Button as part of the Label but hide it like so:
Button(action: {
showIndex = i
showSheet = true
}, label: {
HStack {
Text("\(array[i])")
Text(showIndex.description)
.hidden()
}
})
This doesn't change your UI, but you use it so it gets properly updated. I can't seem to find where I had the issue in my app, and I have changed the UI to get away from this, but I can't remember how I did it. I will update this if I can find it. This is a bit of a kludge, but it works.
Passing a binding to the index fix the issue like this
struct ContentView: View {
#State var array = ["First", "Second", "Third"]
#State var showIndex: Int = 0
#State var showSheet = false
var body: some View {
VStack {
ForEach (0 ..< array.count, id:\.self) { i in
Button(action:{
showIndex = i
showSheet.toggle()
})
{
Text("\(array[i])")
}.sheet(isPresented: $showSheet){
SheetView(text: $array, index: $showIndex)
}
}
}
.padding()
}
}
struct SheetView: View {
#Binding var text: [String]
#Binding var index: Int
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
TextField("text:", text: $text[index])
Button("dismiss") {
presentationMode.wrappedValue.dismiss()
}
}.padding()
}
}
In SwiftUI2 when calling isPresented if you don't pass bindings you're going to have some weird issues.
This is a simple tweak if you want to keep it with the isPresented and make it work but i would advise you to use the item with a costum struct like the answer of swiftPunk
This is how I would do it. You'll lose your form edits if you don't use #State variables.
This Code is Untested
struct SheetView: View {
#Binding var text: String
#State var draft: String
#Environment(\.presentationMode) var presentationMode
init(text: Binding<String>) {
self._text = text
self._draft = State(initialValue: text.wrappedValue)
}
var body: some View {
VStack {
TextField("text:", text: $draft)
Button("dismiss") {
text = draft
presentationMode.wrappedValue.dismiss()
}
}.padding()
}
}
I'm trying to remove top safe area. Is there any way to remove top safe area from top and image?
Code:-
struct ContentView22: View {
#State private var showDialog = false
var body: some View {
ZStack {
ScrollView {
VStack {
Image("CentrImg.jpeg")
.resizable()
.scaledToFill()
.frame(width:UIScreen.screenWidth,height: 180, alignment: .center)
.clipped()
.ignoresSafeArea()
.edgesIgnoringSafeArea(.top)
VStack(alignment:.leading,spacing:25) {
Text("Some text")
.onTapGesture {
showDialog = true
}
}
}
}
.alert(isPresented: $showDialog,TextAlert(title: "Title",message: "Message") { result in
print(result as Any)
if let _ = result {
} else {
}
})
}.edgesIgnoringSafeArea(.top)
.background(Color.red)
.foregroundColor(.white)
.navigationBarHidden(true)
.edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
.navigationBarBackButtonHidden(true)
.navigationBarTitle("", displayMode: .inline)
}
}
Alert Control Class:-
import SwiftUI
import Combine
public struct TextAlert {
public var title: String // Title of the dialog
public var message: String // Dialog message
public var placeholder: String = "" // Placeholder text for the TextField
public var accept: String = "OK" // The left-most button label
public var cancel: String? = "Cancel" // The optional cancel (right-most) button label
public var secondaryActionTitle: String? = nil // The optional center button label
public var action: (String?) -> Void // Triggers when either of the two buttons closes the dialog
public var secondaryAction: (() -> Void)? = nil // Triggers when the optional center button is tapped
}
extension UIAlertController {
convenience init(alert: TextAlert) {
self.init(title: alert.title, message: alert.message, preferredStyle: .alert)
addTextField {
$0.placeholder = alert.placeholder
$0.returnKeyType = .done
}
if let cancel = alert.cancel {
addAction(UIAlertAction(title: cancel, style: .cancel) { _ in
alert.action(nil)
})
}
if let secondaryActionTitle = alert.secondaryActionTitle {
addAction(UIAlertAction(title: secondaryActionTitle, style: .default, handler: { _ in
alert.secondaryAction?()
}))
}
let textField = self.textFields?.first
addAction(UIAlertAction(title: alert.accept, style: .default) { _ in
alert.action(textField?.text)
})
}
}
struct AlertWrapper<Content: View>: UIViewControllerRepresentable {
#Binding var isPresented: Bool
let alert: TextAlert
let content: Content
func makeUIViewController(context: UIViewControllerRepresentableContext<AlertWrapper>) -> UIHostingController<Content> {
UIHostingController(rootView: content)
}
final class Coordinator {
var alertController: UIAlertController?
init(_ controller: UIAlertController? = nil) {
self.alertController = controller
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: UIViewControllerRepresentableContext<AlertWrapper>) {
uiViewController.rootView = content
if isPresented && uiViewController.presentedViewController == nil {
var alert = self.alert
alert.action = {
self.isPresented = false
self.alert.action($0)
}
context.coordinator.alertController = UIAlertController(alert: alert)
uiViewController.present(context.coordinator.alertController!, animated: true)
}
if !isPresented && uiViewController.presentedViewController == context.coordinator.alertController {
uiViewController.dismiss(animated: true)
}
}
}
extension View {
public func alert(isPresented: Binding<Bool>, _ alert: TextAlert) -> some View {
AlertWrapper(isPresented: isPresented, alert: alert, content: self)
}
}
Output with alert code
Output without alert code:-
Can someone please explain to me how to remove top safe area from image with alert code, I've tried to implement by above but no results yet.
Any help would be greatly appreciated.
Thanks in advance.
I removed your Alert code. You can do the same with a much simpler function.
Value
#State var testText: String = ""
Alert Func
func alertView() {
let alert = UIAlertController(title: "Test", message: "Test Message", preferredStyle: .alert)
alert.addTextField { (testTextField) in
testTextField.placeholder = "Test TextField"
}
let okButton = UIAlertAction(title: "OK", style: .default) { (_) in
self.testText = alert.textFields?[0].text ?? ""
}
let cancellButton = UIAlertAction(title: "Cancel", style: .destructive) { (_) in
}
alert.addAction(okButton)
alert.addAction(cancellButton)
UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: {
})
}
Using:
Text("Some text")
.onTapGesture {
alertView()
}
this is a Macos app where the parsclass is setup in a previous view that contains the YardageRowView below. That previous view is responsible for changing the contents of the parsclass. This is working is other views that use a NavigationLink to display the views.
When the parsclass is changed, this view is refreshed, but the previous value is put in the text field on the holeValueTestView.
I cannot comprehend how the value is not being passed into the holeValueTestView correctly
This is a view shown as a .sheet, and if I dismiss it and display it again, everything is fine.
if you create a macOS project called YardageSample and replace the ContentView.swift and YardageSampleApp.swift with the two files below, you can see that the display in red changes and the black numbers do not change until you click Done and redisplay the .sheet
//
// YardageSampleApp.swift
// YardageSample
//
// Created by Brian Quick on 2021-04-12.
//
import SwiftUI
#main
struct YardageSampleApp: App {
#StateObject var parsclass = parsClass()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(parsclass)
}
}
}
//
// ContentView.swift
// YardageSample
//
// Created by Brian Quick on 2021-04-12.
//
import SwiftUI
struct ContentView: View {
#StateObject var parsclass = parsClass()
enum ActiveSheet : String , Identifiable {
case CourseMaintenance
var id: String {
return self.rawValue
}
}
#State var activeSheet : ActiveSheet? = nil
var body: some View {
Button(action: {
self.activeSheet = .CourseMaintenance
}) {
Text("Course Maintenance")
}
.sheet(item: $activeSheet) { sheet in
switch sheet {
case .CourseMaintenance:
CourseMaintenance()
}
}.frame(width: 200, height: 200, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
}
}
class parsClass: ObservableObject {
#Published var pars = [parsRec]()
init() {
self.pars = [parsRec]()
self.pars.append(parsRec())
}
func create(newpars: [parsRec]) {
pars.removeAll()
pars = newpars
}
}
class parsRec: Identifiable, Codable {
var id = UUID()
var Hole = 1
var Yardage = 1
}
struct CourseMaintenance: View {
#EnvironmentObject var parsclass: parsClass
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Button(action: {presentationMode.wrappedValue.dismiss()}, label: {
Text("Done")
})
Button(action: {switchScores(number: 1)}, label: {
Text("Button 1")
})
Button(action: {switchScores(number: 2)}, label: {
Text("Button 2")
})
Button(action: {switchScores(number: 3)}, label: {
Text("Button 3")
})
CourseDetail().environmentObject(parsclass)
}.frame(width: 400, height: 400, alignment: .center)
}
func switchScores(number: Int) {
var newparRecs = [parsRec]()
for i in 0..<17 {
let myrec = parsRec()
myrec.Hole = i
myrec.Yardage = number
newparRecs.append(myrec)
}
parsclass.create(newpars: newparRecs)
}
}
struct CourseDetail: View {
#EnvironmentObject var parsclass: parsClass
var body: some View {
HStack(spacing: 0) {
ForEach(parsclass.pars.indices, id: \.self) { indice in
// this displays the previous value
holeValueTestView(value: String(parsclass.pars[indice].Yardage))
// this displays the correct value after parsclass has changed
Text(String(parsclass.pars[indice].Yardage))
.foregroundColor(.red)
}
}
}
}
struct holeValueTestView: View {
#State var value: String
var body: some View {
//TextField(String(value), text: $value)
Text(value)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
There are a couple of issues going on:
You have multiple instances of parsClass. One is defined in YardageSampleApp and passed into the view hierarchy as a #EnvironmentObject. The second is defined in ContentView as a #StateObject. Make sure you're only using one.
On holeValueTestView, you defined value as a #State variable. That gets set initially when the view is created by its parent and then it maintains its own state. So, when the environmentObject changed, because it was in charge of its own state at this point, it didn't update the value. You can simply remove #State and see the behavior that you want.
struct ContentView: View {
#EnvironmentObject var parsclass : parsClass //<-- Here
enum ActiveSheet : String , Identifiable {
case CourseMaintenance
var id: String {
return self.rawValue
}
}
#State var activeSheet : ActiveSheet? = nil
var body: some View {
Button(action: {
self.activeSheet = .CourseMaintenance
}) {
Text("Course Maintenance")
}
.sheet(item: $activeSheet) { sheet in
switch sheet {
case .CourseMaintenance:
CourseMaintenance()
}
}.frame(width: 200, height: 200, alignment: .center)
}
}
struct holeValueTestView: View {
var value: String //<-- Here
var body: some View {
Text(value)
}
}
As a side note:
In Swift, normally type names are capitalized. If you want to write idiomatic Swift, you would change your parsClass to ParsClass for example.
I trying to learn the new SwiftUI coding technique. I would like to click a button that will add elements to an array that is a #State variable. The button is the buttonclick function. The array is the push_group_row / push_group_array. I get an error in the append statement.
Eventually the buttonclick will access a database to build an array with more row, but for now I just trying to add one row.
Code:
import SwiftUI
import Combine
var gordon: String = "xxxxxx"
struct Result: Codable {
let trackId: Int
let trackName: String
let collectionName: String
}
struct Response: Codable {
var results: [Result]
}
struct Pokemon: Identifiable {
let id: Int
let name: String
let type: String
let color: Color
}
struct push_group_row {
let id: Int
let code: String
let title: String
}
struct ContentView: View
{
#State private var results = [Result]()
#State var pokemonList = [
Pokemon(id: 0, name: "Charmander", type: "Fire", color: .red),
Pokemon(id: 1, name: "Squirtle", type: "Water", color: .blue),
Pokemon(id: 2, name: "Bulbasaur", type: "Grass", color: .green),
Pokemon(id: 3, name: "Pikachu", type: "Electric", color: .yellow),]
#State var push_group_array = [push_group_row(id: 0, code: "code12", title: "POAFire")]
var body: some View
{
NavigationView
{
VStack(alignment: . leading){
Button(action: {
// What to perform
self.buttonclick()
}) {
// How the button looks like
Text("clickme")
.background(Color.purple)
.foregroundColor(.white)
}
List(results, id: \.trackId)
{item in
NavigationLink(destination: DetailView(lm: String(item.trackId)))
{
VStack(alignment: .leading)
{
Text(String(item.trackId))
Text(item.trackName)
.font(.headline)
Text(item.collectionName)
Text(gordon)
}
}
}
List(self.pokemonList)
{ pokemon in
HStack
{
Text(pokemon.name)
Text(pokemon.type).foregroundColor(pokemon.color)
}
}
List(push_group_array, id: \.id)
{ pg_item in
HStack
{
Text(String(pg_item.id))
Text(pg_item.code)
}
}
.onAppear(perform: self.loaddata)
}
.navigationBarTitle("x")
.navigationBarItems(
trailing: Button(action: addPokemon, label: { Text("Add") }))
Spacer()
}
}
func addPokemon() {
if let randomPokemon = pokemonList.randomElement() {
pokemonList.append(randomPokemon)
}
}
// *************************** below is the add arrat code
func buttonclick() {
let newCode = "First"
let newTitle = "Second"
push_group_array.append(id: 1, code: newCode, title: newTitle)
}
func loaddata()
{
print("loaddata")
guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song")
else
{
print("Invalid URL")
return
}
var urlData: NSData?
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data
{
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data)
{
DispatchQueue.main.async
{
urlData = data as NSData?
self.results = decodedResponse.results
print(self.results)
print(urlData ?? "urlData_Defaultvalue")
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You need to push the object rather than 3 values
push_group_array.append(push_group_row(id: 1, code: newCode, title: newTitle))