I want to make a custom text field which will display the amount and (%) symbol
can anyone please tell me how can i acheive this.
if i enter 12 it should auto insert 12%
in UIKit it will be like textField.text = "(text) %"
struct UiTextFieldRepresentable: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> some UIView {
let textField = UITextField(frame: .zero)
textField.placeholder = "Enter your text"
textField.text = "\(text) %"
return textField
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
issue with this code is it is showing % sign before i start writing.
all i want want is when i start writing in the field it should postfix the % sign
TextField("", value: $input, format: .percent )
for this i made a function and on right side put a image as percentage that fixed my issue
func makeUIView(context: Context) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.tintColor = UIColor.clear
textField.rightView = makeImageForTextField()
textField.rightViewMode = .always
textField.keyboardType = .decimalPad
let toolBar = UIToolbar(
frame: CGRect(
x: 0, y: 0,
width: textField.frame.size.width,
height: 44
)
)
let doneButton = UIBarButtonItem(
title: "Done",
style: .done,
target: self,
action: #selector(textField.doneButtonTapped(button:))
)
toolBar.items = [doneButton]
toolBar.setItems([doneButton], animated: true)
textField.inputAccessoryView = toolBar
return textField
}
private func makeImageForTextField() -> UIButton {
let button = UIButton()
button.setImage(UIImage(systemName: "percent"), for: .normal)
button.tintColor = .black
button.isUserInteractionEnabled = false
return button
}
For the cases like this, please show your code. Because I'm not sure this is an actual question, maybe you haven't even started coding before asking it.
What exactly are you doing? To display the % symbol, just put in in the text (there're no formatting issues, it's not a special symbol etc):
struct ContentView: View {
#State var number: Int = 12
var body: some View {
Text("$ \(number) %")
.padding()
}
}
And you'll get your view rendered:
Related
I am trying to make a WYSIWYG editor by interfacing between SwiftUI and UIKit via a UIViewRepresentable. I am primarily using SwiftUI but am using UIKit here as it seems SwiftUI does not currently support the functionality needed.
My problem is, when I set the NSMutableAttributedString to be already containing a string with attributes, if I then select that text in the UIViewRepresentable before typing any new text and press the underline button in the UIToolBar to add the attribute, the attribute is added to the NSMutableAttributedString but the UIView does not update to show the updated NSMutableAttributedString. However, if I type a single character and then select the text and add the underline attribute, the UIView updates.
Could someone explain why this is and maybe point me towards a solution? Any help would be greatly appreciated.
Below is the code:
import SwiftUI
import UIKit
struct ContentView: View {
#State private var mutableAttributedString: NSMutableAttributedString = NSMutableAttributedString(
string: "this is the string before typing anything new",
attributes: [.foregroundColor: UIColor.blue])
var body: some View {
EditorExample(outerMutableString: $mutableAttributedString)
}
}
struct EditorExample: UIViewRepresentable {
#Binding var outerMutableString: NSMutableAttributedString
#State private var outerSelectedRange: NSRange = NSRange()
func makeUIView(context: Context) -> some UITextView {
// make UITextView
let textView = UITextView()
textView.font = UIFont(name: "Helvetica", size: 30.0)
textView.delegate = context.coordinator
// make toolbar
let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: textView.frame.size.width, height: 44))
// make toolbar underline button
let underlineButton = UIBarButtonItem(
image: UIImage(systemName: "underline"),
style: .plain,
target: context.coordinator,
action: #selector(context.coordinator.underline))
toolBar.items = [underlineButton]
textView.inputAccessoryView = toolBar
return textView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.attributedText = outerMutableString
}
func makeCoordinator() -> Coordinator {
Coordinator(innerMutableString: $outerMutableString, selectedRange: $outerSelectedRange)
}
class Coordinator: NSObject, UITextViewDelegate {
#Binding var innerMutableString: NSMutableAttributedString
#Binding var selectedRange: NSRange
init(innerMutableString: Binding<NSMutableAttributedString>, selectedRange: Binding<NSRange>) {
self._innerMutableString = innerMutableString
self._selectedRange = selectedRange
}
func textViewDidChange(_ textView: UITextView) {
innerMutableString = textView.textStorage
}
func textViewDidChangeSelection(_ textView: UITextView) {
selectedRange = textView.selectedRange
}
#objc func underline() {
if (selectedRange.length > 0) {
innerMutableString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: selectedRange)
}
}
}
}
It's not working because NSAttributedString is a class and #State is for value types like structs. This means the dependency tracking is broken and things won't update correctly.
Also your UIViewRepresentable and Coordinator design is non-standard so I thought I would share an example of the correct way to do it. The binding is change to a string, which is a value type so it's working (minus the underline feature obviously).
struct ContentView: View {
//#State private var mutableAttributedString: NSMutableAttributedString = NSMutableAttributedString(
// string: "this is the string before typing anything new",
// attributes: [.foregroundColor: UIColor.blue])
#State var string = "this is the string before typing anything new"
var body: some View {
VStack {
// EditorExample(outerMutableString: $mutableAttributedString)
// EditorExample(outerMutableString: $mutableAttributedString) // a second to test bindings are working\
//Text(mutableAttributedString.string)
EditorExample(outerMutableString2: $string)
EditorExample(outerMutableString2: $string)
}
}
}
struct EditorExample: UIViewRepresentable {
//#Binding var outerMutableString: NSMutableAttributedString
#Binding var outerMutableString2: String
// this is called first
func makeCoordinator() -> Coordinator {
// we can't pass in any values to the Coordinator because they will be out of date when update is called the second time.
Coordinator()
}
// this is called second
func makeUIView(context: Context) -> UITextView {
context.coordinator.textView
}
// this is called third and then repeatedly every time a let or `#Binding var` that is passed to this struct's init has changed from last time.
func updateUIView(_ uiView: UITextView, context: Context) {
//uiView.attributedText = outerMutableString
uiView.text = outerMutableString2
// we don't usually pass bindings in to the coordinator and instead use closures.
// we have to set a new closure because the binding might be different.
context.coordinator.stringDidChange2 = { string in
outerMutableString2 = string
}
}
class Coordinator: NSObject, UITextViewDelegate {
lazy var textView: UITextView = {
let textView = UITextView()
textView.font = UIFont(name: "Helvetica", size: 30.0)
textView.delegate = self
// make toolbar
let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: textView.frame.size.width, height: 44))
// make toolbar underline button
let underlineButton = UIBarButtonItem(
image: UIImage(systemName: "underline"),
style: .plain,
target: self,
action: #selector(underline))
toolBar.items = [underlineButton]
textView.inputAccessoryView = toolBar
return textView
}()
//var stringDidChange: ((NSMutableAttributedString) -> ())?
var stringDidChange2: ((String) -> ())?
func textViewDidChange(_ textView: UITextView) {
//innerMutableString = textView.textStorage
//stringDidChange?(textView.textStorage)
stringDidChange2?(textView.text)
}
func textViewDidChangeSelection(_ textView: UITextView) {
// selectedRange = textView.selectedRange
}
#objc func underline() {
let range = textView.selectedRange
if (range.length > 0) {
textView.textStorage.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range)
// stringDidChange?(textView.textStorage)
}
}
}
}
I found this code on the web :
struct CustomTextField: UIViewRepresentable {
#Binding var text: String
#State var placeholder: String
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.borderStyle = .roundedRect
textField.placeholder = placeholder
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.keyboardType = .URL
textField.frame = CGRect(x: 0, y: 0, width: 20.00, height: 10)
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ view: UITextField, context: Context) {
view.text = text
}
}
extension CustomTextField {
class Coordinator: NSObject, UITextFieldDelegate {
#Binding var text: String
init(text: Binding<String>) {
_text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
DispatchQueue.main.async {
self.text = textField.text ?? ""
}
}
}
}
The code works absolutely fine. The problem with this is that I am not able to find a suitable way to increase the height of this. As you can see, I tried to use a CGRect as the frame, to no effect. How can I change the size (particularly height in my specific scenario) of this custom UIViewRepresentable?
Just the same you would do with any other SwiftUI view:
CustomTextField(text: $text, placeholder: "")
// constant
.frame(height: 100)
// fill available height
.frame(maxHeight: .infinity)
If you wanna make it respect intrinsicContentSize, check out this answer
I wondered how to add Buttons and Links to a SwiftUI Text. Some Example: In a long Text, some special Words are Buttons, or Links, like in a Wikipedia Article:
There are some of the Words blue marked as links, how can I reach that in SwiftUI?
Thanks, Boothosh
I know the pain! I have spend a lot of time on internet reading articles on how to do the same and came across the easiest solution.
Reference for the solution here
1. Add TextView(...) to your project
/// Text view with click able links
struct TextView: UIViewRepresentable {
#Binding var text: String
#Binding var textStyle: UIFont.TextStyle
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.font = UIFont.preferredFont(forTextStyle: textStyle)
textView.autocapitalizationType = .sentences
textView.isSelectable = true
textView.isUserInteractionEnabled = true
textView.isEditable = false
textView.dataDetectorTypes = .link
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
}
func makeCoordinator() -> Coordinator {
Coordinator($text)
}
class Coordinator: NSObject, UITextViewDelegate {
var text: Binding<String>
init(_ text: Binding<String>) {
self.text = text
}
func textViewDidChange(_ textView: UITextView) {
self.text.wrappedValue = textView.text
}
}
}
Full Usage:
struct DetailsView: View {
#State var text : String = "Yo.. try https://google.com"
#State private var textStyle = UIFont.TextStyle.body
var body: some View {
TextView(text: $text, textStyle: $textStyle)
}
}
I am using a UITextView in a SwiftUI app in order to get a list of editable, multiline text fields based on this answer: How do I create a multiline TextField in SwiftUI?
I use the component in SwiftUI like this:
#State private var textHeight: CGFloat = 0
...
GrowingField(text: $subtask.text ?? "", height: $textHeight, changed:{
print("Save...")
})
.frame(height: textHeight)
The GrowingField is defined like this:
struct GrowingField: UIViewRepresentable {
#Binding var text: String
#Binding var height: CGFloat
var changed:(() -> Void)?
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.isScrollEnabled = false
textView.backgroundColor = .orange //For debugging
//Set the font size and style...
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
if uiView.text != self.text{
uiView.text = self.text
}
recalculateHeight(textView: uiView, height: $height)
}
func recalculateHeight(textView: UITextView, height: Binding<CGFloat>) {
let newSize = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
if height.wrappedValue != newSize.height {
DispatchQueue.main.async {
height.wrappedValue = newSize.height
}
}
}
//Coordinator and UITextView delegates...
}
The problem I'm having is that sizeThatFits calculates the correct height at first, then replaces it with an incorrect height. If I print the newSize inside recalculateHeight() it goes like this when my view loads:
(63.0, 34.333333333333336) <!-- Right
(3.0, 143.33333333333334) <!-- Wrong
(3.0, 143.33333333333334) <!-- Wrong
I have no idea where the wrong size is coming from, and I don't know why the right one is replaced. This is how it looks with the height being way too big:
If I make a change to it, the recalculateHeight() method gets called again via textViewDidChange() and it rights itself:
This is really hacky, but if I put a timer in makeUIView(), it fixes itself as well:
//Eww, gross...
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
recalculateHeight(view: textView, height: $height)
}
Any idea how I can determine where the incorrect sizeThatFits value is coming from and how I can fix it?
It took me a long time to arrive at a solution for this. It turns out the UITextView sizing logic is good. It was a parent animation that presents my views that was causing updateUIView to fire again with in-transition UITextView size values.
By setting .animation(.none) on the parent VStack that holds all my text fields, it stopped the propagation of the animation and now it works. 🙂
On a view I have something like this:
TextFieldUsername()
this shows something like
So, this view shows an icon and the textfield username.
Below that, I have another one for the password.
Making that username field in focus is unnecessarily hard. The textfield is not small, but making the username field to focus is a matter of tapping on the exact position and perhaps you have to tap 2 or 3 times to make it happen.
I would like to make the whole TextFieldUsername() tappable or to increase the hit area of that textfield. I would like better to make the whole thing tappable and once tapped, make its textfield in focus.
This is TextFieldUsername
struct TextFieldUsername: View {
#State var username:String
var body: some View {
HStack {
Image(systemName: "person.crop.circle")
.renderingMode(.template)
.foregroundColor(.black)
.opacity(0.3)
.fixedSize()
TextField(TextFieldUsernameStrings.username, text: $username)
.textFieldStyle(PlainTextFieldStyle())
.textContentType(.username)
.autocapitalization(.none)
}
}
}
Is that possible in SwiftUI without using any external library like introspect?
Using a custom TextField like the one from Matteo Pacini
you can do something like this:
struct CustomTextField1: UIViewRepresentable {
class Coordinator: NSObject, UITextFieldDelegate {
#Binding var text: String
var didBecomeFirstResponder = false
init(text: Binding<String>) {
_text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
text = textField.text ?? ""
}
}
#Binding var text: String
var isFirstResponder: Bool = false
func makeUIView(context: UIViewRepresentableContext<CustomTextField1>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
return textField
}
func makeCoordinator() -> CustomTextField1.Coordinator {
return Coordinator(text: $text)
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField1>) {
uiView.text = text
if isFirstResponder && !context.coordinator.didBecomeFirstResponder {
uiView.becomeFirstResponder()
context.coordinator.didBecomeFirstResponder = true
}
}
}
struct ContentView : View {
#State var text: String = ""
#State var isEditing = false
var body: some View {
CustomTextField1(text: $text, isFirstResponder: isEditing)
.frame(width: 300, height: 50)
.background(Color.red)
.onTapGesture {
isEditing.toggle()
}
}
}
It's a bit complex, but should get the work done. As for a pure SwiftUI answer, it's currently unavailable.