I think there's a bug that disabled text editor is focussed when I get in and out into another tab. I want to totally disable text editors but I don't know how.
Seeing is believing.
struct TabViewWithTextEditor: View {
var body: some View {
TabView {
TextEditors()
.tabItem {
Image(systemName: "text.bubble")
Text("Text Editor")
}
AnotherView()
.tabItem {
Image(systemName: "shippingbox")
Text("Empty View")
}
}
}
}
struct TextEditors: View {
#State var textA: String = "Hello World"
#State var textB: String = "Placeholder"
#State var enabled: Bool = true
init() {
UITextView.appearance().backgroundColor = .clear // To apply background color.
}
var body: some View {
VStack {
Text("Text Editor")
TextEditor(text: $textA)
.background(enabled ? .gray : .red)
.foregroundColor(.black)
.disabled(!enabled)
TextEditor(text: $textB)
.background(enabled ? .yellow : .red)
.foregroundColor(.black)
.disabled(!enabled)
Toggle("Enable Text Editors", isOn: $enabled)
}
.padding(30)
}
}
struct AnotherView: View {
var body: some View {
Text("Empty View")
}
}
And it looks like
It looks like the issue is due to TextEditor (or UITextView at backend) preserves focus, probably due to a bug.
Here is safe workaround - remove focus forcefully before disable
Tested with Xcode 13.4 / iOS 15.5
#FocusState private var focused: Bool
var isEditing: Binding<Bool> {
Binding(get: { enabled } , set: {
if !$0 {
focused = false // << here !!
}
enabled = $0
})
}
// ...
Toggle("Enable Text Editors", isOn: isEditing)
Related
I am having a problem while I want to dismiss a popup (that appears automatically depending on a specific condition) by clicking a button.
This is the PopUp struct:
struct dataPrivacyPopUp: View {
let model: OffersView.Model
let termsOfUseText = "Nutzungsbedingungen"
let privacyPolicyText = "Datenschutzerklärung"
#State var termsOfUseChecked = false
#State var privacyPolicyChecked = false
#State var buttonDisabled = true
#State private var showPopUp: Bool = false
#Binding var showModal: Bool
var body: some View {
ZStack {
if ( model.showPopUp == true) {
// PopUp Window
VStack(alignment: .center){
Image("logo")
.aspectRatio(contentMode: .fill)
.frame(alignment: .center)
.padding()
VStack(alignment: .leading) {
Text((model.acceptance?.salutation)!)
.multilineTextAlignment(.leading)
.padding()
.foregroundColor(Color.black)
Text((model.acceptance?.statement)!)
.multilineTextAlignment(.leading)
.padding()
.foregroundColor(Color.black)
Text((model.acceptance?.declarationIntro)!)
.multilineTextAlignment(.leading)
.padding()
.foregroundColor(Color.black)
if ((model.acceptance?.dpr)! == true) {
VStack(alignment: .leading){
HStack {
CheckBoxView(checked: $privacyPolicyChecked)
HStack(spacing: 0){
Text(R.string.localizable.dataPrivacyPopupText())
.foregroundColor(Color.black)
Button(privacyPolicyText) {
model.openUrl(url: API.privacyPolicyURL)
}
}
}
Text((model.acceptance?.declarationOutro)!)
.multilineTextAlignment(.leading)
.padding()
}
.padding()
Button(action: {
model.setTos()
print("showModal PopUpView2 1: \(showModal)")
self.showModal.toggle()
print("showModal PopUpView2 2: \(showModal)")
}, label: {
Text(R.string.localizable.dataPrivacyButton())
.foregroundColor(Color.white)
.font(Font.system(size: 23, weight: .semibold))
})
.disabled(model.buttonDisabledForOne(privacyPolicyChecked: privacyPolicyChecked, termsOfUseChecked: termsOfUseChecked))
.padding()
}
}
}
// .onAppear(perform: )
.background(Color.white01)
.padding()
}
}
}
}
and this is where I call it (contentView):
struct OffersView: View {
#StateObject var model = Model()
#State private var showingPopUp = false
#State private var showModal = false
#State private var showingAddUser = false
// var showPopup : Bool = true
var body: some View {
NavigationView {
Group {
switch model.sections {
case .loading:
ActivityIndicator(animate: true)
case .success(let sections):
ScrollView(.vertical) {
VStack(alignment: .leading, spacing: 0) {
Text(R.string.localizable.offersHello(model.firstName))
.aplFont(.headline02)
.padding(.bottom, 24)
VStack(spacing: 48) {
ForEach(sections) { section in
OffersSectionView(section: section, model: model)
}
}
}
.useFullWidth(alignment: .leading)
.padding()
}
default:
Color.clear
if ( model.showPopUp == true) {
ZStack {
Color.black.opacity(model.showPopUp ? 0.3 : 0).edgesIgnoringSafeArea(.all)
dataPrivacyPopUp(model: model, showModal: self.$showModal)
.onAppear(perform: {
self.showModal.toggle()
})
}
}
}
}
.navigationBarHidden(true)
.handleNavigation(model.navigationPublisher)
.onAppear(perform: model.onAppear)
.onDisappear(perform: model.onDisappear)
.environment(\.dynamicTypeEnabled, false)
.safariView(isPresented: model.showSafari) {
SafariView(url: model.safariUrl!)
}
}
}
}
I need help about this, I tried the traditional method to set a #Binding variable etc .. but that's not working, the boolean value is changing but the UI is not updating (the popup is not dismissing), thank you
I tried to look at your code - I suggest you simplify it to the bare minimum to exemplify your issue - and it seems that you are using 2 properties to show your pop-up: showingPopUp and showModal. It is quite likely that you are having trouble keeping them both in sync.
For starters, I would suggest to use only one variable, either it is true or false - "a man with two watches never knows what time it is".
For the solution:
If you prefer keeping your ZStack approach, the solution would look something like:
struct MyPrivacy: View {
#Binding var showMe: Bool
var body: some View {
VStack {
Text("The content of the pop-up")
.padding()
Button {
withAnimation {
showMe.toggle()
}
} label: {
Text("Dismiss")
}
}
}
}
struct Offers: View {
#State private var showPopup = false
var body: some View {
NavigationView {
ZStack {
VStack {
Text("View behind the pop-up")
.padding()
Button {
withAnimation {
showPopup.toggle()
}
} label: {
Text("Pop")
}
}
if showPopup {
Color.white
MyPrivacy(showMe: $showPopup)
}
}
}
}
}
If instead you want to go for a more flexible approach, if you are developing for iOS, SwiftUI has a convenient object - Sheets. You can use it as suggested in the documentation, or build a specific struct that manages all the modal views of this type and use your model to handle the presentation.
The process goes like:
Create a struct that will handle all kinds of Sheets of your app.
Add to your view-model the property to present any sheet.
Create the Views that will be the content of each sheet.
Call the .sheet(item:content:) method on each View the requires a sheet.
Here's the sample code:
SheetView handler:
struct SheetView: Identifiable {
// This struct controls what modal view will be presented.
// The enum SheetScreenType can grow to as many as different
// modal views your app needs - add the content in the switch below.
let id = UUID()
var screen: SheetScreenType
#ViewBuilder
var content: some View {
switch screen {
case .dataPrivacy:
DataPrivacy()
default:
EmptyView()
}
}
enum SheetScreenType {
case dataPrivacy
case none
}
}
Presenter in your view-model:
class MyViewModel: ObservableObject {
// This code can fit anywhere within your view-model.
// It controls the presentation of the modal view, which in
// this case is a Sheet.
private let sharedSheet = SheetView(screen: .none)
// Show the selected sheet
#Published var sheetView: SheetView?
var showSheet: SheetView.SheetScreenType {
get {
return sheetView?.screen ?? .none
}
set {
switch newValue {
case .none:
sheetView = nil
default:
sheetView = sharedSheet
}
sheetView?.screen = newValue
}
}
}
Content of your modal view:
struct DataPrivacy: View {
#EnvironmentObject var model: MyViewModel // Pass YOUR model here
var body: some View {
VStack(alignment: .center){
Text("Respecting your privacy, no details are shown here")
.padding()
Button {
print("Anything you need")
// Set the showSheet property of your model to
// present a modal view. Setting it to .none dismisses
// the modal view.
model.showSheet = .none
} label: {
Text("Time do dismiss the modal view")
}
.padding()
}
}
}
Enable your view to listen to your model to present the sheet:
struct OffersView: View {
#ObservedObject var model = MyViewModel() // Pass YOUR model here
var body: some View {
VStack {
Text("Anything you wish")
.padding()
Button {
withAnimation {
// Set the showSheet property of your model to
// present a modal view. Set it to any choice
// among the ones in the SheetScreen.SheetScreenType enum.
model.showSheet = .dataPrivacy
}
} label: {
Text("Tap here for the privacy in modal view")
}
}
// Show a modal sheet.
// Add this property at the top level of every view that
// requires a modal view presented - whatever content it might have.
.sheet(item: $model.sheetView) { sheet in
sheet.content
.environmentObject(model)
}
}
}
Good luck with your project!
I try to change the view state according to edit mode, when is editing hide the view and when it's not editing show the view, when I use on change and print the edit mode value its work but its doesn't work when working with views.
struct TaskStateMark_Row: View {
#ObservedObject var task: Task
#Environment(\.editMode) private var editMode
var body: some View {
Group {
// Show and hide the view according to edit mode state
if let editMode = editMode?.wrappedValue {
if editMode == .inactive {
taskState
.onTapGesture(perform: onTapAction)
}
}
}
}
private var taskState: some View {
Group {
if task.isCompleted {
completedState
} else {
incompletedState
}
}
.frame(width: 44, height: 44)
}
private var incompletedState: some View {
ZStack{
fillCircle
circle
}
}
private var circle: some View {
Image(systemName: "circle")
.font(.system(size: 24))
.foregroundColor(task.wrappedPriority.color)
}
private var fillCircle: some View {
Image(systemName: "circle.fill")
.font(.system(size: 24))
.foregroundColor(task.wrappedPriority.color.opacity(0.15))
}
private var completedState: some View {
Image(systemName: "checkmark.circle.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(.white, task.wrappedPriority.color)
.font(.system(size: 24))
}
}
I found the answer in the developer documentation in the follow link
https://developer.apple.com/documentation/swiftui/editmode
The code
#Environment(\.editMode) private var editMode
#State private var name = "Maria Ruiz"
var body: some View {
Form {
if editMode?.wrappedValue.isEditing == true {
TextField("Name", text: $name)
} else {
Text(name)
}
}
.animation(nil, value: editMode?.wrappedValue)
.toolbar { // Assumes embedding this view in a NavigationView.
EditButton()
}
}
My custom text editor below once you click on the pen to edit, a new space appears so the text from before is not on the same line as the new one. How can I fix this? Here's a simple reproducible example:
struct SwiftUIView: View {
#State var name: String = "test"
#State var showEdit: Bool = true
var body: some View {
HStack {
HStack {
if(showEdit) {
CustomTextEditor.init(placeholder: "My unique name", text: $name)
.font(.headline)
} else {
Text(name)
.font(.headline)
}
}
Spacer()
Button(action: {
showEdit.toggle()
}) {
Image(systemName: "pencil")
.foregroundColor(.secondary)
}
}
}
}
struct CustomTextEditor: View {
let placeholder: String
#Binding var text: String
var body: some View {
ZStack {
if text.isEmpty {
Text(placeholder)
.foregroundColor(Color.primary.opacity(0.25))
}
TextEditor(text: $text)
}.onAppear() {
UITextView.appearance().backgroundColor = .clear
}.onDisappear() {
UITextView.appearance().backgroundColor = nil
}
}
}
I want it to have the same padding properies as inserting a simple Text("") so when I switch between Text("xyz") and TextEditor(text: $xyz) it has the same padding alignment. Right now TextEditor has a weird padding.
You will drive yourself insane trying to line up a Text and a TextEditor (or a TextField, for that matter), so don't try. Use another, disabled, TextEditor instead, and control the .opacity() on the top one depending upon whether the bound variable is empty or not. Like this:
struct CustomTextEditor: View {
#Binding var text: String
#State private var placeholder: String
init(placeholder: String, text: Binding<String>) {
_text = text
_placeholder = State(initialValue: placeholder)
}
var body: some View {
ZStack {
TextEditor(text: $placeholder)
.disabled(true)
TextEditor(text: $text)
.opacity(text == "" ? 0.7 : 1)
}
}
}
This view will show the placeholder if there is no text, and hide the placeholder as soon as there is text.
Edit:
You don't need the button, etc. in your other view. It becomes simply:
struct SwiftUIView: View {
#State var name: String = ""
var body: some View {
CustomTextEditor.init(placeholder: "My unique name", text: $name)
.font(.headline)
.padding()
}
}
and if you need a "Done" button on the keyboard, change your CustomTextEditor() to this:
struct CustomTextEditor: View {
#Binding var text: String
#State private var placeholder: String
#FocusState var isFocused: Bool
init(placeholder: String, text: Binding<String>) {
_text = text
_placeholder = State(initialValue: placeholder)
}
var body: some View {
ZStack {
TextEditor(text: $placeholder)
.disabled(true)
TextEditor(text: $text)
.opacity(text == "" ? 0.7 : 1)
.focused($isFocused)
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button {
isFocused = false
} label: {
Text("Done")
.foregroundColor(.accentColor)
.padding(.trailing)
}
}
}
}
}
How can I change the Color from the Button in a Alert and the back Button from NavigationLink? To set .accentColor(.init("someColor")) after the Text from the Alert Button doesn't work. To set .accentColor(.init("someColor")) after navigationLink does not work too. What can I do?
The Alert Button:
The Back Button from NavigationLink:
struct ContentView: View {
#State var showSheet = false
#State var alertShouldBeShown = !UserDefaults.standard.bool(forKey: "£Start")
var body: some View {
Button(action: {
self.showSheet.toggle()
}) {
Text("click me")
//.accentColor(colorScheme == .dark ? Image("") : Image("Info-Icon"))
}.sheet(isPresented: $showSheet) {
SheetView(showSheet: self.$showSheet)
}
.alert(isPresented: $alertShouldBeShown, content: {
Alert(title: Text("Headline"),
message: Text("Text"),
dismissButton: Alert.Button.default(
Text("Ok"), action: {
UserDefaults.standard.set(true, forKey: "Start")
}
)
)
})
}
}
struct SheetView: View {
#Binding var showSheet: Bool
var body: some View {
NavigationView {
DetailView()
.navigationBarTitle(Text("Headline"), displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
self.showSheet = false
}) {
Text("Done")
.bold()
})
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct DetailView: View {
var body: some View {
List {
HStack {
NavigationLink(destination: DetailViewOne()) {
Text("View 1")
}
}
HStack {
NavigationLink(destination: DetailViewTwo()) {
Text("View 2")
}
}
}
}
}
struct DetailViewOne: View {
var body: some View {
Text("1")
}
}
struct DetailViewTwo: View {
var body: some View {
Text("2")
}
}
You can change the NavigationBar accent color by using accentColor but you need to apply it to the SheetView (the root view of a given environment):
SheetView(showSheet: self.$showSheet)
.accentColor(.red)
Unfortunately SwiftUI doesn't allow much of Alert customisation so far.
However, as Alert is built on top of UIKit components (UIAlertController) this also means you can change the appearance of UIView when contained in UIAlertController.
You can put this code in your #main App init or in the SceneDelegate:
UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = .red
I am building some basic form functionality in my app at the moment and I am having trouble with TextFields not changing the value in the relevant binded variable when tapping another TextField or pressing "Done" in Edit Mode.
#Binding var jobDetails: JobDetails
#Environment(\.colorScheme) var colorScheme: ColorScheme
...
var body: some View {
...
HStack {
Text("Hourly Rate")
Spacer()
TextField("", value: $jobDetails.hourlyRateBasic, formatter: TextFormatters().currencyFormatter())
.keyboardType(.asciiCapableNumberPad)
.multilineTextAlignment(.trailing)
...
In the iOS simulator, the field only seems to update when I physically hit the return key on my keyboard after typing in a new value (not the soft keyboard in the simulator). I would like the TextField to commit it's change to jobDetails.hourlyRateBasic when tapping another TextField or pressing "Done" to exit edit mode.
It seems that onEditingChanged fires when I tap another TextField, but I don't know how to leverage that into changing the jobDetails with the new value.
This is typical behavior of TextField in SwiftUI. Following is an example of it and alternative method to make TextField more responsive while typing.
import SwiftUI
struct ContentView: View {
#State private var text: String = "0"
#State private var num: Int = 0
private var resultString: String {
if let num = Int(self.text) {
return String(num*num)
}
return "0"
}
private var resultInt: Int {
return self.num*self.num
}
var body: some View {
VStack {
VStack(alignment:.leading) {
Text("Input number as String")
TextField("String Number",text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text("Input number as Int")
TextField("Int Number", value: self.$num, formatter: NumberFormatter())
.textFieldStyle(RoundedBorderTextFieldStyle())
}
Spacer()
Text("From String")
Text("Square of \(self.text) is \(self.resultString)") .font(.title)
Spacer()
Text("From Int")
Text("Square of \(self.num) is \(self.resultInt)") .font(.title)
Spacer()
}.padding()
}
}
This is already fixed. Tested with Xcode 13.3 / iOS 15.4
struct TestView: View {
#State private var value1 = 1.0
#State private var text = ""
private var currencyFormatter: NumberFormatter = {
var nf = NumberFormatter()
nf.numberStyle = .currency
return nf
}()
var body: some View {
VStack {
HStack {
Text("Hourly Rate [\(value1)]")
Spacer()
TextField("", value: $value1, formatter: currencyFormatter)
.keyboardType(.asciiCapableNumberPad)
.multilineTextAlignment(.trailing)
}
HStack {
Text("Other")
Spacer()
TextField("Enter something", text: $text)
}
}
}
}