How to remove Top Safe Area in Zstack With ScrollView Image SwiftUI - image

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()
}

Related

how to clear textfield after message sent

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")
}
}
}
}

SwiftUI: Sharing NSSharingService on macOS not receiving share

I have a simple test application that attempts to share a CoreData record with another user using NSSharingService. I can create my share and that works,
but when I try to receive the share it opens up the application but doesn't do anything.
I have added CKSharingSupported to my plist.
I have also followed this link to no avail:
CloudKit CKShare userDidAcceptCloudKitShareWith Never Fires on Mac App
Here is my code:
SharingServiceApp:
final class AppDelegate: NSObject, NSApplicationDelegate
{
func application(_ application: NSApplication, userDidAcceptCloudKitShareWith metadata: CKShare.Metadata)
{
print ("userDidAcceptCloudKitShareWith")
let shareStore = persistenceController.sharedPersistentStore!
persistenceController.container.acceptShareInvitations(from: [metadata], into: shareStore)
{ _, error in
if let error = error
{
print("acceptShareInvitation error :\(error)")
}
}
}
}
ContentView:
import SwiftUI
import CoreData
import CloudKit
let persistenceController = PersistenceController()
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink {
VStack
{
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
Button("Share")
{
shareRecord(item: item)
}
}
} label: {
Text(item.timestamp!, formatter: itemFormatter)
}
}
}
.toolbar {
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
func shareRecord(item: Item)
{
Task
{
if let share = await createShare(item: item)
{
let container = CKContainer(identifier: "iCloud.com.xxxxxxxx.sharingservice")
let item = NSItemProvider()
item.registerCloudKitShare(share, container: container)
DispatchQueue.main.async
{
let sharingService = NSSharingService(named: .cloudSharing)
sharingService!.perform(withItems: [item])
}
}
}
}
private func createShare(item: Item) async -> CKShare?
{
do
{
let (_, share, _) = try await persistenceController.container.share([item], to: nil)
share[CKShare.SystemFieldKey.title] = "MyApp"
return share
}
catch
{
print("Failed to create share")
return nil
}
}
OK I have finally managed to get userDidAcceptCloudKitShareWith to be called.
You need to create the app delegate for your SwiftUI app using #NSApplicationDelegateAdaptor:
#main
struct Sharing_ServiceApp: App
{
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene
{
WindowGroup
{
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
I put that line in and my code instantly started receiving the share requests.

Show a sheet in response to a drop

I'm implementing drag and drop, and have a case where I need the user to decide what to do in response to a drop. So I want to bring up a sheet to ask the user for input. The problem is that the sheet doesn't appear until I drag another item to the same view. This does make sense, so I'm looking for a way to handle this differently.
The current approach looks like this (simplified):
struct SymbolInfo {
enum SymbolType {
case string, systemName
}
var type: SymbolType
var string: String
}
struct MyView: View, DropDelegate {
#State var sheetPresented = false
#State var droppedText = ""
static let dropTypes = [UTType.utf8PlainText]
var textColor = NSColor.white
private var frameRect: CGRect = .null
private var contentPath: Path = Path()
private var textRect: CGRect = .null
#State private var displayOutput: SymbolInfo
#State private var editPopoverIsPresented = false
// There's an init to set up the display output, the various rects and path
var body: some View {
ZStack(alignment: stackAlignment) {
BackgroundView() // Draws an appropriate background
.frame(width: frameRect.width, height: frameRect.height)
if displayOutput.type == .string {
Text(displayOutput.string)
.frame(width: textRect.width, height: textRect.height, alignment: .center)
.foregroundColor(textColour)
.font(displayFont)
.allowsTightening(true)
.lineLimit(2)
.minimumScaleFactor(0.5)
}
else {
Image(systemName: displayOutput.string)
.frame(width: textRect.width, height: textRect.height, alignment: .center)
.foregroundColor(textColour)
.minimumScaleFactor(0.5)
}
}
.onAppear {
// Retrieve state information from the environment
}
.focusable(false)
.allowsHitTesting(true)
.contentShape(contentPath)
.onHover { entered in
// Populates an inspector
}
.onTapGesture(count: 2) {
// Handle a double click
}
.onTapGesture(count: 1) {
// Handle a single click
}
.popover(isPresented: $editPopoverIsPresented) {
// Handles a popover for editing data
}
.onDrop(of: dropTypes, delegate: self)
.sheet(sheetPresented: $sheetPresented, onDismiss: sheetReturn) {
// sheet to ask for the user's input
}
}
func sheetReturn() {
// act on the user's input
}
func performDrop(info: DropInfo) -> Bool {
if let item = info.itemProviders(for: dropTypes).first {
item.loadItem(forTypeIdentifier: UTType.utf8PlainText.identifier, options: nil) { (textData, error) in
if let textData = String(data: textData as! Data, encoding: .utf8) {
if (my condition) {
sheetIsPresented = true
droppedText = textData
}
else {
// handle regular drop
}
}
}
return true
}
return false
}
}
So my reasoning is that the drop sets sheetPresented to true, but then it doesn't get acted on until the view is rebuilt, such as on dragging something else to it. But I'm still new to SwiftUI, so I may be incorrect.
Is there a way to handle this kind of interaction that I haven't found?
I never was able to exactly reproduce the problem, but the issue related to trying to have more than one kind of sheet that could be shown, depending on conditions. The solution was to break up the original view into a family of views that encapsulated the different behaviours, and show the appropriate one rather than try to make one view do everything.
I won't show the whole code, since it's too deeply embedded in the app, but here's a demo app that works correctly:
import SwiftUI
import UniformTypeIdentifiers
#main
struct DragAndDropSheetApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
HStack() {
TargetView(viewType: .normal, viewText: "A")
.frame(width: 40, height: 40, alignment: .top)
TargetView(viewType: .protected, viewText: "B")
.frame(width: 40, height: 40, alignment: .top)
TargetView(viewType: .normal, viewText: "C")
.frame(width: 40, height: 40, alignment: .top)
TargetView(viewType: .protected, viewText: "D")
.frame(width: 40, height: 40, alignment: .top)
}
.padding()
}
}
enum ViewType {
case normal, protected
}
struct TargetView: View, DropDelegate {
#State private var sheetPresented = false
#State var viewType: ViewType
#State var viewText: String
#State private var dropText = ""
#State private var dropType: DropActions = .none
static let dropTypes = [UTType.utf8PlainText]
var body: some View {
ZStack(alignment: .center) {
Rectangle()
.foregroundColor(viewType == .normal ? .blue : .red)
Text(viewText)
.foregroundColor(.white)
.frame(width: nil, height: nil, alignment: .center)
}
.focusable(false)
.allowsHitTesting(true)
.onDrop(of: TargetView.dropTypes, delegate: self)
.sheet(isPresented: $sheetPresented, onDismiss: handleSheetReturn) {
ProtectedDrop(isPresented: $sheetPresented, action: $dropType)
}
}
func handleSheetReturn() {
switch dropType {
case .append:
viewText += dropText
case .replace:
viewText = dropText
case .none:
// Nothing to do
return
}
}
func performDrop(info: DropInfo) -> Bool {
if let item = info.itemProviders(for: TargetView.dropTypes).first {
item.loadItem(forTypeIdentifier: UTType.utf8PlainText.identifier, options: nil) { textData, error in
if let textData = String(data: textData as! Data, encoding: .utf8) {
if viewType == .normal {
viewText = textData
}
else {
dropText = textData
sheetPresented = true
}
}
}
return true
}
return false
}
}
enum DropActions: Hashable {
case append, replace, none
}
struct ProtectedDrop: View {
#Binding var isPresented: Bool
#Binding var action: DropActions
var body: some View {
VStack() {
Text("This view is protected. What do you want to do?")
Picker("", selection: $action) {
Text("Append the dropped text")
.tag(DropActions.append)
Text("Replace the text")
.tag(DropActions.replace)
}
.pickerStyle(.radioGroup)
HStack() {
Spacer()
Button("Cancel") {
action = .none
isPresented.toggle()
}
.keyboardShortcut(.cancelAction)
Button("OK") {
isPresented.toggle()
}
.keyboardShortcut(.defaultAction)
}
}
.padding()
}
}

SwiftUI: How it refreshes view and why #Published ObservableObject properties works randomly

SwiftUI seems to me more and more confusing in the way it works.
At first glance it seams fast and easy to grasp. But if you add more and more views
something that seems to be simple starts to behave very odd and take many time to solve.
I have Input field with validation. This is customized input with that I can reuse in many places. But on different screens this can work totally different and totally unreliable.
View with form
struct LoginView {
#ObservedObject private var viewModel = LoginViewModel()
var body: some View {
VStack(spacing: 32) {
Spacer()
LabeledInput(label: "Email", input: self.$viewModel.email, isNuemorphic: true, rules: LoginFormRules.email, validation: self.$viewModel.emailValidation)
.textContentType(.emailAddress)
.keyboardType(.emailAddress)
.autocapitalization(.none)
.frame(height: 50)
LabeledInput(label: "Password", isSecure: true, input: self.$viewModel.password, isNuemorphic: true, rules: LoginFormRules.password, validation: self.$viewModel.passwordValidation)
.textContentType(.password)
.keyboardType(.asciiCapable)
.autocapitalization(.none)
.frame(height: 50)
self.makeSubmitButton()
Spacer()
}
}
LabeledInput - resuable custom input view with validation support
struct LabeledInput: View {
// MARK: - Properties
let label: String?
let isSecure: Bool
// MARK: - Binding
#Binding var input: String
var isEditing: Binding<Bool>?
// MARK: - Actions
private let onEditingChanged: (Bool) -> Void
private let onCommit: () -> Void
// MARK: - Validation
#ObservedObject var validator: FieldValidator<String>
// MARK: - Init
init(label: String? = nil,
isSecure: Bool = false,
input: Binding<String>,
isEditing: Binding<Bool>? = nil,
// validation
rules: [Rule<String>] = [],
validation: Binding<Validation>? = nil,
// actions
onEditingChanged: #escaping (Bool) -> Void = { _ in },
onCommit: #escaping () -> Void = { }) {
self.label = label
self.isSecure = isSecure
self._input = input
self.isEditing = isEditing
self.onEditingChanged = onEditingChanged
self.onCommit = onCommit
self.validator = FieldValidator(input: input, rules: rules, validation: validation ?? .constant(Validation()))
}
var useUIKit: Bool {
self.isEditing != nil
}
var body: some View {
GeometryReader { geometry in
ZStack {
RoundedRectangle(cornerRadius: 4.0)
.stroke(lineWidth: 1)
.foregroundColor(!self.validator.validation.isEdited ? Color("LightGray")
: self.validator.validation.isValid ? Color("Green") : Color("Red"))
.frame(maxHeight: geometry.size.height)
.offset(x: 0, y: 16)
VStack {
HStack {
self.makeLabel()
.offset(x: self.isNuemorphic ? 0 : 16,
y: self.isNuemorphic ? 0 : 8)
Spacer()
}
Spacer()
}
self.makeField()
.frame(maxHeight: geometry.size.height)
.offset(x: 0, y: self.isNuemorphic ? 20 : 16)
.padding(10)
}
}
}
private func makeField() -> some View {
Group {
if useUIKit {
self.makeUIKitTextField(secure: self.isSecure)
} else {
if self.isSecure {
self.makeSecureField()
} else {
self.makeTextField()
}
}
}
}
private func makeLabel() -> some View {
Group {
if label != nil {
Text("\(self.label!.uppercased())")
.font(.custom("AvenirNext-Regular", size: self.isNuemorphic ? 13 : 11))
.foregroundColor(!self.validator.validation.isEdited ? Color("DarkBody")
: self.validator.validation.isValid ? Color("Green") : Color("Red"))
.padding(.horizontal, 8)
} else {
EmptyView()
}
}
}
private func makeSecureField() -> some View {
SecureField("", text: self.$input, onCommit: {
self.validator.onCommit()
self.onCommit()
})
.font(.custom("AvenirNext-Regular", size: 15))
.foregroundColor(Color("DarkBody"))
.frame(maxWidth: .infinity)
}
private func makeTextField() -> some View {
TextField("", text: self.$input, onEditingChanged: { editing in
self.onEditingChanged(editing)
self.validator.onEditing(editing)
if !editing { self.onCommit() }
}, onCommit: {
self.validator.onCommit()
self.onCommit()
})
.font(.custom("AvenirNext-Regular", size: 15))
.foregroundColor(Color("DarkBody"))
.frame(maxWidth: .infinity)
}
private func makeUIKitTextField(secure: Bool) -> some View {
let firstResponderBinding = Binding<Bool>(get: {
self.isEditing?.wrappedValue ?? false //?? self.isFirstResponder
}, set: {
//self.isFirstResponder = $0
self.isEditing?.wrappedValue = $0
})
return UIKitTextField(text: self.$input, isEditing: firstResponderBinding, font: UIFont(name: "AvenirNext-Regular", size: 15)!, textColor: UIColor(named: "DarkBody")!, placeholder: "", onEditingChanged: { editing in
self.onEditingChanged(editing)
self.validator.onEditing(editing)
}, onCommit: {
self.validator.onCommit()
self.onCommit()
})
}
}
And here is how I store model (input values and validation) in ObservableObject i.e. LoginViewModel.
final class LoginViewModel: ObservableObject {
// MARK: - Published
#Published var email: String = ""
#Published var password: String = ""
#Published var emailValidation: Validation = Validation(onEditing: true)
#Published var passwordValidation: Validation = Validation(onEditing: true)
#Published var validationErrors: [String]? = nil
#Published var error: DescribableError? = nil
}
When I use this code depending on how I create ViewModel (in LoginView property or injected to LoginView constructor) depending on view parent views (screens) it is embedded can work totally different can cause hours of debugging and unexpected behaviour.
Sometimes it seems that there is 1 ViewModel instance sometimes it seems that this instance is created with each View refresh
sometimes LabeledInput body is refreshing and validation colouring of label works corretly. Other times it seems it does not refresh at all and nothing happens
sometimes refreshes so often keyboard is immediately hiding
Other times there is no validation at all
Other times input is lost after exiting field or when rotating phone landscape to portrait
If there is some event that causes parent view refresh it can cause the inputs to lose data and validation.
Sometimes it refreshes to often other times it doesn't refresh at all as it should.
I've tried to add .id(UUID) , custom .id(refreshId) or other Equatable protocol implementations but it doesn't work as expected to be reusable customized input with validation reusable between multiple forms on multiple screens.
Here is simple validation struct
struct Validation {
let onEditing: Bool
init(onEditing: Bool = false) {
self.onEditing = onEditing
}
var isEdited: Bool = false
var errors: [String] = []
}
And here FieldValidator ObservableObject
class FieldValidator<T>: ObservableObject {
// MARK: - Properties
private let rules: [Rule<T>]
// MARK: - Binding
#Binding private var input: T
#Binding var validation: Validation
// MARK: - Init
init(input: Binding<T>, rules: [Rule<T>], validation: Binding<Validation>) {
#if DEBUG
print("[FieldValidator] init: \(input.wrappedValue)")
#endif
self._input = input
self.rules = rules
self._validation = validation
}
private var disposables = Set<AnyCancellable>()
}
// MARK: - Public API
extension FieldValidator {
func validateField() {
validation.errors = rules
.filter { !$0.isAsync }
.filter { !$0.validate(input) }
.map { $0.errorMessage() }
}
func validateFieldAsync() {
rules
.filter { $0.isAsync }
.forEach { rule in
rule.validateAsync(input)
.filter { valid in
!valid
}.sink(receiveValue: { _ in
self.validation.errors.append(rule.errorMessage())
})
.store(in: &disposables)
}
}
}
// MARK: - Helper Public API
extension FieldValidator {
func onEditing(_ editing: Bool) {
self.validation.isEdited = true
if editing {
if self.validation.onEditing {
self.validateField()
}
} else {
// on end editing
self.validateField()
self.validateFieldAsync()
}
}
func onCommit() {
self.validateField()
self.validateFieldAsync()
}
}
Rules are just subclasses of
class Rule<T> {
var isAsync: Bool { return false }
func validate(_ value: T) -> Bool { return false }
func errorMessage() -> String { return "" }
func validateAsync(_ value: T) -> AnyPublisher<Bool, Never> {
fatalError("Async validation is not implemented!")
}
}
UPDATE
Complete UIKitTextField example
#available(iOS 13.0, *)
struct UIKitTextField: UIViewRepresentable {
// MARK: - Observed
#ObservedObject private var keyboardEvents = KeyboardEvents()
// MARK: - Binding
#Binding var text: String
var isEditing: Binding<Bool>?
// MARK: - Actions
let onBeginEditing: () -> Void
let onEndEditing: () -> Void
let onEditingChanged: (Bool) -> Void
let onCommit: () -> Void
// MARK: - Proprerties
private let keyboardOffset: CGFloat
private let textAlignment: NSTextAlignment
private let font: UIFont
private let textColor: UIColor
private let backgroundColor: UIColor
private let contentType: UITextContentType?
private let keyboardType: UIKeyboardType
private let autocorrection: UITextAutocorrectionType
private let autocapitalization: UITextAutocapitalizationType
private let isSecure: Bool
private let isUserInteractionEnabled: Bool
private let placeholder: String?
public static let defaultFont = UIFont.preferredFont(forTextStyle: .body)
private var hasDoneToolbar: Bool = false
init(text: Binding<String>,
isEditing: Binding<Bool>? = nil,
keyboardOffset: CGFloat = 0,
textAlignment: NSTextAlignment = .left,
font: UIFont = UIKitTextField.defaultFont,
textColor: UIColor = .black,
backgroundColor: UIColor = .white,
contentType: UITextContentType? = nil,
keyboardType: UIKeyboardType = .default,
autocorrection: UITextAutocorrectionType = .default,
autocapitalization: UITextAutocapitalizationType = .none,
isSecure: Bool = false,
isUserInteractionEnabled: Bool = true,
placeholder: String? = nil,
hasDoneToolbar: Bool = false,
onBeginEditing: #escaping () -> Void = { },
onEndEditing: #escaping () -> Void = { },
onEditingChanged: #escaping (Bool) -> Void = { _ in },
onCommit: #escaping () -> Void = { }) {
self._text = text
self.isEditing = isEditing
self.keyboardOffset = keyboardOffset
self.onBeginEditing = onBeginEditing
self.onEndEditing = onEndEditing
self.onEditingChanged = onEditingChanged
self.onCommit = onCommit
self.textAlignment = textAlignment
self.font = font
self.textColor = textColor
self.backgroundColor = backgroundColor
self.contentType = contentType
self.keyboardType = keyboardType
self.autocorrection = autocorrection
self.autocapitalization = autocapitalization
self.isSecure = isSecure
self.isUserInteractionEnabled = isUserInteractionEnabled
self.placeholder = placeholder
self.hasDoneToolbar = hasDoneToolbar
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
textField.delegate = context.coordinator
textField.keyboardType = keyboardType
textField.textAlignment = textAlignment
textField.font = font
textField.textColor = textColor
textField.backgroundColor = backgroundColor
textField.textContentType = contentType
textField.autocorrectionType = autocorrection
textField.autocapitalizationType = autocapitalization
textField.isSecureTextEntry = isSecure
textField.isUserInteractionEnabled = isUserInteractionEnabled
//textField.placeholder = placeholder
if let placeholder = placeholder {
textField.attributedPlaceholder = NSAttributedString(
string: placeholder,
attributes: [
NSAttributedString.Key.foregroundColor: UIColor.lightGray
])
}
textField.addTarget(context.coordinator, action: #selector(Coordinator.valueChanged(_:)), for: .editingChanged)
keyboardEvents.didShow = {
if textField.isFirstResponder {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(350)) {
textField.adjustScrollView(offset: self.keyboardOffset, animated: true)
}
}
}
if hasDoneToolbar {
textField.addDoneButton {
print("Did tap Done Toolbar button")
textField.resignFirstResponder()
}
}
return textField
}
func updateUIView(_ textField: UITextField, context: Context) {
textField.text = text
if let isEditing = isEditing {
if isEditing.wrappedValue {
textField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
}
}
final class Coordinator: NSObject, UITextFieldDelegate {
let parent: UIKitTextField
init(_ parent: UIKitTextField) {
self.parent = parent
}
#objc func valueChanged(_ textField: UITextField) {
parent.text = textField.text ?? ""
parent.onEditingChanged(true)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
parent.onBeginEditing()
parent.onEditingChanged(true)
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
//guard textField.text != "" || parent.shouldCommitIfEmpty else { return }
DispatchQueue.main.async {
self.parent.isEditing?.wrappedValue = false
}
parent.text = textField.text ?? ""
parent.onEditingChanged(false)
parent.onEndEditing()
parent.onCommit()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
parent.isEditing?.wrappedValue = false
textField.resignFirstResponder()
parent.onCommit()
return true
}
}
}
extension UIView {
func adjustScrollView(offset: CGFloat, animated: Bool = false) {
if let scrollView = findParent(of: UIScrollView.self) {
let contentOffset = CGPoint(x: scrollView.contentOffset.x, y: scrollView.contentOffset.y + offset)
scrollView.setContentOffset(contentOffset, animated: animated)
} else {
print("View is not in ScrollView - do not adjust content offset")
}
}
}
Here is sample EmailRule implementation
class EmailRule : RegexRule {
static let regex = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
public convenience init(message : String = "Email address is invalid"){
self.init(regex: EmailRule.regex, message: message)
}
override func validate(_ value: String) -> Bool {
guard value.count > 0 else { return true }
return super.validate(value)
}
}

Show/hide navigationBarItems when keyboard visible/non-visible | SwiftUI

I've spent a lot of time researching and playing with different codes to make this work but can't seem to figure it out. Below you'll see two pieces of code, one of me creating the "Done" bar button item and the other is checking to see if keyboard is present and to close it when the button is clicked. The one issue I'm having is, I only want the bar button to show when the keyboard is present. I want the bar button hidden once the keyboard is gone or prior to even opening up the keyboard in the first place. How do you do that nowadays with SwiftUI?
.navigationBarItems(trailing:
Button(action: {
if UIApplication.shared.isKeyboardPresented {
UIApplication.shared.endEditing()
}
}, label: {
Text("Done")
})
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
/// Checks if view hierarchy of application contains `UIRemoteKeyboardWindow` if it does, keyboard is presented
var isKeyboardPresented: Bool {
if let keyboardWindowClass = NSClassFromString("UIRemoteKeyboardWindow"),
self.windows.contains(where: { $0.isKind(of: keyboardWindowClass) }) {
return true
} else {
return false
}
}
}
As an option, you can define an object to listen to keyboard notifications:
class KeybordManager: ObservableObject {
static let shared = KeybordManager()
#Published var keyboardFrame: CGRect? = nil
init() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(willHide), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
#objc func willHide() {
self.keyboardFrame = .zero
}
#objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
self.keyboardFrame = keyboardScreenEndFrame
}
}
Then inside your view subscribe to keyboardFrame updates and show and hide the Done button accordingly:
public struct ContentView: View {
#State private var showDoneButton = false
#State private var text = ""
public var body: some View {
NavigationView {
TextField("Some text", text: $text)
.navigationBarItems(trailing:
Group {
if self.showDoneButton {
Button(action: {
UIApplication.shared.endEditing()
}, label: {
Text("Done")
})
} else {
EmptyView()
}
}
)
.onReceive(KeybordManager.shared.$keyboardFrame) { keyboardFrame in
if let keyboardFrame = keyboardFrame, keyboardFrame != .zero {
self.showDoneButton = true
} else {
self.showDoneButton = false
}
}
}
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}

Resources