I have three sets of Text and TextField. And I need to filter each TextField entry. I have gotten a function to filter the TextField entry from this website (https://zenn.dev/yorifuji/articles/swiftui-textfield-filter). Finally, I have the following lines of code.
import SwiftUI
struct ContentView: View {
@State var username = ""
@State var password = ""
@State var tenantID = ""
var body: some View {
VStack {
makeForm(label: "Username: ", placeHolder: "123456", text: $username)
makeForm(label: "Password: ", placeHolder: "abcdefg", text: $password)
makeForm(label: "Shop ID: ", placeHolder: "123456", text: $tenantID)
}.padding(.horizontal, 40.0)
}
@ViewBuilder
private func makeForm(label: String, placeHolder: String, text: Binding<String>) -> some View {
HStack {
let newText = Binding<String>(
get: { text.wrappedValue },
set: { filter(value: $0) }
)
Text(label)
TextField(placeHolder, text: newText)
}
}
func filter(value: String) -> String {
let validCodes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
let sets = CharacterSet(charactersIn: validCodes)
return String(value.unicodeScalars.filter(sets.contains).map(Character.init))
}
}
Well, I don't know how to use the Binding with get and set, which I believe is what I need. Yet, I get a warning at the following line.
set: { filter(value: $0) }
What I need to do is set the filtered value to each TextField. What am I doing wrong? Thanks.
Post
Replies
Boosts
Views
Activity
I have the following lines of code to show multiple instances of View (MyTextView)
import SwiftUI
struct ContentView: View {
@State var myTextViews = [MyTextView]()
var body: some View {
VStack {
Button {
} label: {
Text("Show me your current text strings")
}.padding(.vertical, 10.0)
VStack {
ForEach(0 ..< myTextViews.count, id: \.self) { _ in
MyTextView()
}
}
Button {
myTextViews.append(MyTextView())
} label: {
Text("Add me!")
}.padding(.vertical, 10.0)
}
}
}
struct MyTextView: View {
@State var text = ""
var body: some View {
ZStack {
TextField("Enter some text", text: $text)
}.padding(.horizontal, 50.0)
}
}
According to the screenshot, I have three instances, each of which contains TextField. After I tap the top button (Show me your current...), I want to show the result from each TextField. How can I do that? Thanks.
I have a @State variable with an array of strings with which to create instances of TextField. So far, I have the following lines of code.
import SwiftUI
struct ContentView: View {
@State private var names: [String] = ["Jim Thorton", "Susan Murphy", "Tom O'Donnell", "Nancy Smith"]
var body: some View {
HStack {
ForEach($names, id: \.self) { $name in
TextField("", text: $name)
.fixedSize()
.padding(.horizontal, 20.0)
.background(Color.orange.opacity(0.2))
}
}
}
}
I wonder if there is a simple way of aligning instances of TextField horizontally such that one that exceeds the screen width will go to the next line like the following picture?
Thanks.
I have an array of a model with just a single string with which I want to create instances of TextField. And I get an error for the TextField string binding. I know that is wrong. But how can fix it so that I can use textModel.name as a Binding?
import SwiftUI
struct ContentView: View {
@State var textModels = [TextModel]()
var body: some View {
HStack {
ForEach(textModels.indices, id: \.self) { index in
let textModel = textModels[index]
TextField("", text: textModel.name) // <----- Cannot convert value of type 'String' to expected argument type 'Binding<String>'
}
}.background(Color.green)
.onAppear {
textModels.append(TextModel(name: "Jim Thorton"))
textModels.append(TextModel(name: "Susan Murphy"))
textModels.append(TextModel(name: "Tom O'Donnell"))
textModels.append(TextModel(name: "Nancy Smith"))
}
}
}
struct TextModel: Hashable {
let name: String
}
Thanks.
I have a simple project where I have a UUID string followed by a tap button as shown below. If one taps the Add me, the app will list a new instance of a View (KeywordRow).
The following is what I have.
import SwiftUI
struct ContentView: View {
@ObservedObject var monster: Monster
var body: some View {
VStack {
Button {
monster.items.append(Keyword())
} label: {
Text("Add me!")
}.padding(.vertical, 10.0)
ForEach($monster.items) { item in
KeywordRow(id: item.id)
}
}
}
}
// MARK: - ObservableObject
class Monster: ObservableObject {
@Published var items = [Keyword]()
}
// MARK: - Keyword
struct Keyword: Identifiable {
var id = UUID()
}
struct KeywordRow: View {
@Binding var id: UUID
var body: some View {
VStack {
HStack {
Text("ID: \(id)")
Button {
/* ------ Delete ------ */
} label: {
Text("Delete")
}
}
}
}
}
My question is how I can let the app delete the corresponding instance when I tap the Delete button? I have an ObservedObject variable, which I haven't used. Thanks.
I'm trying to show a dialog over ContentView. The dialog view, ShowDialogView, has an ObservedObject object with name and properties.
class User: ObservableObject {
@Published var name = ""
@Published var age: Int = 0
}
struct ShowDialogView: View {
@Binding var isPresented: Bool
@ObservedObject var user: User
/*
@State var name = ""
*/
init(isPresented: Binding<Bool>, user: User) {
self._isPresented = isPresented
self.user = user.searchWord
//_name = State(initialValue: "Kimberly")
}
var body: some View {
VStack {
...
...
}.onAppear {
print("\(user.name)")
}
}
}
struct ContentView: View {
@State var user = User()
@State var showMe = true
var body: some View {
VStack {
...
...
ShowDialogView(isPresented: showMe, user: user)
}
}
}
The dialog view will open with no problem. The problem that I have is that the user object doesn't deliver anything beyond default values. If I somehow set the name property to "Kimberly" before the dialog appears, the app will end up showing no value (""). Even if I try setting an initial value to the name property in the constructor, the app will still show an empty value. What am I doing wrong? I'm sorry I cannot give you a lot of details in the code above. Thank you.
I have a simple project as follows.
import SwiftUI
class GameSettings: ObservableObject {
@Published var score: Int = 100
}
struct ContentView: View {
@StateObject var settings = GameSettings()
var body: some View {
GeometryReader { geo in
ZStack {
HStack(spacing: 0.0) {
RightView().environmentObject(GameSettings())
.frame(width: geo.size.width / 2.0, height: geo.size.height)
Spacer()
}
VStack {
HStack {
Spacer()
Button {
print("\(settings.score)")
} label: {
Text("Print")
.font(.largeTitle)
}.padding(.trailing, 40.0)
}
Spacer()
}
}
}
}
}
struct RightView: View {
@EnvironmentObject var settings: GameSettings
var body: some View {
ZStack {
Color.red
Button {
settings.score += 100
} label: {
Text("Change")
.font(.largeTitle)
}
}.environmentObject(settings)
}
}
So the score is supposed to increase by 100 if I tap the button over the red area. And I want to print the latest value by tapping the Print button at the top-right corner. But it will remain at 100. What am I doing wrong? And can I achieve my goal without using an @ObservedObject variable? Thanks.
My ContentView has one View (RightView) inside. Tapping the button over RightView, the app will pass a boolean value to ContentView.
struct ContentView: View {
@ObservedObject var monster: MonsterObservable
var body: some View {
GeometryReader { geo in
ZStack {
HStack(spacing: 0.0) {
RightView(showMe: $monster.showDialog)
.frame(width: geo.size.width, height: geo.size.height)
}
ShowDialogView(isShowing: $monster.showDialog) {
}
.frame(width: 500, height: 600, alignment: .center)
.cornerRadius(10.0)
}
}
}
}
struct RightView: View {
@Binding var showMe: Bool
var body: some View {
ZStack {
Color.red
Button {
showMe = true
} label: {
Text("Tap me")
.font(.largeTitle)
}
}
}
}
class MonsterObservable: ObservableObject {
@Published var showDialog = false
}
struct ShowDialogView<Content: View>: View {
@Binding var isShowing: Bool
@ViewBuilder let content: () -> Content
var body: some View {
Group {
if isShowing {
ZStack {
Color.brown
VStack {
Spacer()
Button {
isShowing = false
} label: {
Text("Close me")
.font(.largeTitle)
}.padding([.top, .bottom], 100.0)
}
}
}
}
}
}
So the code above works. If I tap the button over RightView, a small dialog (ShowDialogView) will appear. Currently, ContentView and RightView are bound. That's not exactly what I need. How can I pass a boolean value from RightView to ContentView without Binding where ContentView is the following?
struct ContentView: View {
@ObservedObject var monster: MonsterObservable
var body: some View {
GeometryReader { geo in
ZStack {
HStack(spacing: 0.0) {
RightView()
.frame(width: geo.size.width, height: geo.size.height)
}
ShowDialogView(isShowing: $monster.showDialog) {
}
.frame(width: 500, height: 600, alignment: .center)
.cornerRadius(10.0)
}
}
}
}
I was quite active in writing code in SwiftUI several months ago. I've forgotten how to use an ObservedObject object to channel a variable between two Views.
Anyway, I need to show a dialog over ContentView when I tap a button that is shown over another (RightView).
The following is my code.
// ContentView.swift //
import SwiftUI
class ObserveMonster: ObservableObject {
@Published var showDialog = false
}
struct ContentView: View {
@ObservedObject var observeManiac: ObserveMonster
var body: some View {
GeometryReader { geo in
ZStack {
HStack(spacing: 0.0) {
LeftView()
.frame(width: geo.size.width / 2.0, height: geo.size.height, alignment: .leading)
RightView()
.frame(width: geo.size.width / 2.0, height: geo.size.height, alignment: .trailing)
}
ShowDialogView(isShowing: observeManiac.showDialog) {
}
.frame(width: 500, height: 600, alignment: .center)
.cornerRadius(10.0)
}
}
}
}
struct ShowDialogView<Content: View>: View {
let isShowing: Bool
@ViewBuilder let content: () -> Content
var body: some View {
Group {
if isShowing {
Color.blue
}
}
.animation(.default, value: isShowing)
}
}
// RightView.swift //
import SwiftUI
struct RightView: View {
@StateObject var observeManiac = ObserveMonster()
var body: some View {
ZStack {
Color.red
Button {
observeManiac.showDialog.toggle()
} label: {
Text("Tap me")
.font(.largeTitle)
}
}
}
}
When I tap the button, the dialog (ShowDialogView) is no show. Does anybody now what I'm doing wrong? Thanks a million.
I'm trying to animate a shape. The following is my code.
import SwiftUI
struct ContentView7: View {
@State private var goingLeft = true
@State private var goingUp = true
var body: some View {
ZStack {
Color.green.ignoresSafeArea()
CustomShape(quadDistance: goingUp ? 0: 60.0, horizontalDeviation: 0.0)
.fill(Color.red)
.ignoresSafeArea()
.animation(.easeInOut(duration: 1.0).repeatForever(), value: goingUp)
}.onAppear {
goingUp = false
}
}
}
struct CustomShape: Shape {
var quadDistance: CGFloat
var horizontalDeviation: CGFloat
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: -horizontalDeviation, y: 0))
path.addLine(to: CGPoint(x: rect.maxX + horizontalDeviation , y: 0))
path.addLine(to: CGPoint(x: rect.maxX + horizontalDeviation, y: rect.midY))
path.addLine(to: CGPoint(x: -horizontalDeviation, y: rect.midY))
path.move(to: CGPoint(x: -horizontalDeviation, y: rect.midY))
path.addQuadCurve(to: CGPoint(x: rect.maxX + horizontalDeviation, y: rect.midY), control: CGPoint(x: rect.midX, y: rect.midY + quadDistance))
}
}
}
Well, my intension is to move the center of the convex point up and down. But the shape won't animate itself. What am I doing wrong? Muchos thankos.
Hmm... I don't quite get it. How can I get rid of the deprecation warning in the following case?
import SwiftUI
struct ContentView: View {
@State var isAnimating = false
var body: some View {
Circle()
.fill(Color.pink)
.frame(width: 150, height: 150)
.scaleEffect(isAnimating ? 0.5 : 1.0)
.animation(Animation.easeIn(duration: 1.0).repeatForever())
.onAppear {
self.isAnimating = true
}
}
}
The following could work except that I have to tap the circle once.
import SwiftUI
struct ContentView: View {
@State var isAnimating = true
var body: some View {
Circle()
.fill(Color.pink)
.frame(width: 150, height: 150)
.scaleEffect(isAnimating ? 1 : 0.5)
.animation(Animation.easeIn(duration: 3.0).repeatForever(), value: isAnimating)
.onTapGesture { isAnimating.toggle() }
.onAppear {
isAnimating = false
isAnimating.toggle()
}
}
}
Muchos thankos.
I guess I was in coma, and I didn't learn of MainActor till today.
So I have the following lines of code to test MainActor.
import UIKit
class ViewController: UIViewController {
// MARK: - IBOutlet
@IBOutlet weak var label: UILabel!
// MARK: - IBAction
@IBAction func buttonTapped(_ sender: UIButton) {
Task {
do {
let bool = try await asyncWaitMakeLabelChanges()
if bool {
print("I'm done!")
}
} catch {
print("\(error.localizedDescription)")
}
}
}
func asyncWaitMakeLabelChanges() async throws -> Bool {
for i in 0..<5 {
let text = String(i)
label.text = text // UILabel.text must be used from main thread only
print(text)
sleep(1)
}
return true
}
}
As I expect, I get the purple main thread checker error at the line where I update the label. That's good. And I've changed the code as follows.
import UIKit
class ViewController: UIViewController {
// MARK: - IBOutlet
@IBOutlet weak var label: UILabel!
// MARK: - IBAction
@IBAction func buttonTapped(_ sender: UIButton) {
Task {
do {
let bool = try await asyncWaitMakeLabelChanges()
if bool {
print("I'm done!")
}
} catch {
print("\(error.localizedDescription)")
}
}
}
func asyncWaitMakeLabelChanges() async throws -> Bool {
for i in 0..<5 {
let text = String(i)
Task { @MainActor in
label.text = text
}
print(text)
sleep(1)
}
return true
}
}
Okay. The app won't crash. But the label won't get updated every second. It will finally display the number (4) when the count reaches 4. So my question is why not?
I could change my code as follows to update my label every second.
func asyncWaitMakeLabelChanges() async throws -> Bool {
for i in 0..<5 {
let text = String(i)
DispatchQueue.main.async() { [weak self] in
self?.label.text = text
}
print(text)
sleep(1)
}
return true
}
So why would I want to use MainActor?
I'm trying to subclass UIViewController. And I've written the following.
import UIKit
class BaseViewController: UIViewController {
let titleText: String
init(titleText: String) {
self.titleText = titleText
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
self.titleText = ""
super.init(coder: aDecoder)
setup()
}
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Setup
func setup() {
print("GGG: \(titleText)")
let navBar = navigationController!.navigationBar
navBar.barTintColor = UIColor.yellow
let atext = NSMutableAttributedString(string: titleText)
atext.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.black, range: NSMakeRange(0, atext.length))
atext.addAttribute(NSAttributedString.Key.strokeColor, value: UIColor.gray, range: NSMakeRange(0, atext.length))
atext.addAttribute(NSAttributedString.Key.strokeWidth, value: NSNumber.init(value: -1.0), range: NSMakeRange(0, atext.length))
let titleLabel: UILabel = UILabel(frame: CGRect(origin: CGPoint(x: 15.0, y: 0), size: CGSize(width: 320.0 - 120.0, height: 44.0)))
titleLabel.attributedText = atext
titleLabel.textAlignment = NSTextAlignment.center
titleLabel.font = UIFont(name: "Helvetica", size: 24.0)
self.navigationItem.titleView = titleLabel
}
}
And I subclass it with a view controller named HomeViewController as follows.
import UIKit
class HomeViewController: BaseViewController {
override init(titleText: String) {
super.init(titleText: "Jim")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
But the setup method in BaseViewController never gets the titleText variable ("Jim") from HomeViewController. What am I doing wrong?
Thanks.
When I write code with UIKIt or Cocoa, I usually create and use FileManager.default in AppDelegate or a base view controller. In Cocoa, for example, I would write something like the following.
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let defaultFileManager = FileManager.default
func applicationDidFinishLaunching(_ aNotification: Notification) {
}
func applicationWillTerminate(_ aNotification: Notification) {
NSApp.terminate(nil)
}
}
import Cocoa
class HomeViewController: NSViewController {
let appDelegate = (NSApp.delegate as! AppDelegate)
override func viewDidLoad() {
super.viewDidLoad()
if appDelegate.defaultFileManager.fileExists(atPath: some file path) {
}
}
}
So my question is where I can create FileManager.default so that I use it under different Views in SwiftUI? Muchos thankos.
I need to highlight a string with certain keys. The base string may contain one or more occurrences of a key.
Test 1:
I use code based on this web site .
import SwiftUI
struct HighlightView: View {
let baseText : String = "President's nearly $2 trillion proposed healthcare, climate, and social spending plan has been floundering for over a month since Mr. Williams came out against the bill in late December. That left the party with little to show after six months of negotiations with the conservative Democratic holdout.\n\nHe later left the door cracked open to future negotiations on a separate plan. Mr. Williams has spoken favorably about some chunks of the bill, including a program to establish universal pre-K and climate spending measures.\n\nMr. Williams wields outsized influence over the Democratic Party's agenda since the Senate is split 50-50 between both parties. Senate Democrats can't pass the package without his support, leaving them with no option but to eventually tailor a separate bill that addresses his concerns about its possible impact on the national debt and inflation.\n\nMr. Williams, meanwhile, appears to be focusing most of his time and energy leading a bipartisan group of senators working on election reform proposals including modernizing and updating the Electoral Count Act of 1887."
let keys = ["Democratic", "Williams", "about"]
var body: some View {
ZStack {
Text(baseText) { str in
for i in 0..<keys.count {
let key = keys[i]
if let range = str.range(of: key) {
str[range].foregroundColor = .pink
}
}
}.foregroundColor(Color.gray).font(.system(size: 18.0))
}
}
}
extension Text {
init(_ string: String, configure: ((inout AttributedString) -> Void)) {
var attributedString = AttributedString(string) /// create an `AttributedString`
configure(&attributedString) /// configure using the closure
self.init(attributedString) /// initialize a `Text`
}
}
The following screenshot shows the result. It handles multiple keys. But it highlights one occurrence for each key.
Test 2: I use code based on some web site, which I'm not permitted to show according to this system.
import SwiftUI
struct HighlightView2: View {
let baseText : String = "President's nearly $2 trillion proposed healthcare, climate, and social spending plan has been floundering for over a month since Mr. Williams came out against the bill in late December. That left the party with little to show after six months of negotiations with the conservative Democratic holdout.\n\nHe later left the door cracked open to future negotiations on a separate plan. Mr. Williams has spoken favorably about some chunks of the bill, including a program to establish universal pre-K and climate spending measures.\n\nMr. Williams wields outsized influence over the Democratic Party's agenda since the Senate is split 50-50 between both parties. Senate Democrats can't pass the package without his support, leaving them with no option but to eventually tailor a separate bill that addresses his concerns about its possible impact on the national debt and inflation.\n\nMr. Williams, meanwhile, appears to be focusing most of his time and energy leading a bipartisan group of senators working on election reform proposals including modernizing and updating the Electoral Count Act of 1887."
let keys = ["Democratic", "Williams", "about"]
var body: some View {
ZStack {
ForEach(keys, id: \.self) { key in
highlightedText(baseText: baseText, match: key, highlightColor: Color.orange)
.foregroundColor(Color.gray).font(.system(size: 19.0))
}
}
}
func highlightedText(baseText: String, match: String, highlightColor: Color) -> Text {
guard !baseText.isEmpty && !match.isEmpty else { return Text(baseText) }
var result: Text!
let components = baseText.components(separatedBy: match)
for i in components.indices {
result = (result == nil ? Text(components[i]) : result + Text(components[i]))
if i != components.count - 1 {
result = result + Text(match).foregroundColor(highlightColor)
}
}
return result ?? Text(baseText)
}
}
This one handles multiple occurrences. But I can only use one key at a time. The following screenshot shows the result.
Do you have a better idea in highlighting a string with multiple keys? Muchos thankos for reading.