17.5 C
Canberra
Saturday, January 17, 2026

Issues with keyboard responsiveness in edit sheet [closed]


I’m constructing an app in Swift UI and Swift 6 for iOS 26. I’m utilizing SwiftData to retailer content material. I’ve an edit sheet which I’m fighting.

When the sheet hundreds, the person can’t faucet the TextField to edit instantly; it takes a number of faucets for the keyboard to look, and due to that, the textual content within the subject is highlighted for reduce, copy, and paste. I’ve been at this for days, even utilizing AI to see if it will possibly assist, and I’m no additional ahead.

My EditPolicyView.swift code:

//
//  EditPolicyView.swift
//  Coverage Pal
//
//  Created by Justin Erswell on 09/01/2026.
//

import SwiftUI
import SwiftData
import PhotosUI

// Light-weight attachment abstract - no binary knowledge, simply metadata for show
struct AttachmentSummary: Identifiable, Sendable {
    let id: UUID
    let filename: String
    let mimeType: String
    let isExisting: Bool  // true = already saved in SwiftData, false = newly added

    var isPDF: Bool { mimeType == "software/pdf" }

    // Init for current attachments (extracted values, not the mannequin itself)
    init(id: UUID, filename: String, mimeType: String, isExisting: Bool) {
        self.id = id
        self.filename = filename
        self.mimeType = mimeType
        self.isExisting = isExisting
    }

    // Comfort init for brand new attachments
    init(id: UUID = UUID(), filename: String, mimeType: String) {
        self.id = id
        self.filename = filename
        self.mimeType = mimeType
        self.isExisting = false
    }
}

// Easy worth struct to go knowledge with out SwiftData statement
// NOTE: Attachments are NOT copied right here to keep away from blocking foremost thread with massive binary knowledge
struct EditPolicyData: Identifiable {
    let id: PersistentIdentifier
    var identify: String
    var class: PolicyCategory
    var supplier: String
    var policyNumber: String
    var price: Decimal
    var costFrequency: CostFrequency
    var renewalDate: Date
    var notes: String
    var reminderThirtyDays: Bool
    var reminderFourteenDays: Bool
    var reminderThreeDays: Bool
    var reminderRenewalDay: Bool

    init(from coverage: PolicyItem) {
        let begin = CFAbsoluteTimeGetCurrent()
        self.id = coverage.persistentModelID
        print("⏱️ EditPolicyData: persistentModelID took (CFAbsoluteTimeGetCurrent() - begin)s")

        let t1 = CFAbsoluteTimeGetCurrent()
        self.identify = coverage.identify
        self.class = coverage.class
        self.supplier = coverage.supplier
        self.policyNumber = coverage.policyNumber
        self.price = coverage.price
        self.costFrequency = coverage.costFrequency
        self.renewalDate = coverage.renewalDate
        self.notes = coverage.notes
        print("⏱️ EditPolicyData: primary props took (CFAbsoluteTimeGetCurrent() - t1)s")

        let t2 = CFAbsoluteTimeGetCurrent()
        let schedule = coverage.reminderSchedule
        self.reminderThirtyDays = schedule.thirtyDays
        self.reminderFourteenDays = schedule.fourteenDays
        self.reminderThreeDays = schedule.threeDays
        self.reminderRenewalDay = schedule.renewalDay
        print("⏱️ EditPolicyData: reminderSchedule took (CFAbsoluteTimeGetCurrent() - t2)s")
        print("⏱️ EditPolicyData: TOTAL took (CFAbsoluteTimeGetCurrent() - begin)s")
    }
}

// Wrapper view that passes knowledge to the precise type
struct EditPolicyView: View {
    let knowledge: EditPolicyData

    var physique: some View {
        EditPolicyFormView(
            policyID: knowledge.id,
            initialName: knowledge.identify,
            initialCategory: knowledge.class,
            initialProvider: knowledge.supplier,
            initialPolicyNumber: knowledge.policyNumber,
            initialCost: knowledge.price,
            initialCostFrequency: knowledge.costFrequency,
            initialRenewalDate: knowledge.renewalDate,
            initialNotes: knowledge.notes,
            initialReminderThirtyDays: knowledge.reminderThirtyDays,
            initialReminderFourteenDays: knowledge.reminderFourteenDays,
            initialReminderThreeDays: knowledge.reminderThreeDays,
            initialReminderRenewalDay: knowledge.reminderRenewalDay
        )
    }

    // Comfort init
    init(knowledge: EditPolicyData) {
        self.knowledge = knowledge
    }

    init(coverage: PolicyItem) {
        self.knowledge = EditPolicyData(from: coverage)
    }
}

// Precise type view with inline @State initialization (like AddPolicyView)
struct EditPolicyFormView: View {
    @Atmosphere(.dismiss) non-public var dismiss
    @Atmosphere(.modelContext) non-public var modelContext
    @EnvironmentObject non-public var appSettings: AppSettings

    // Retailer the coverage ID for saving
    let policyID: PersistentIdentifier

    // Preliminary values handed in
    let initialName: String
    let initialCategory: PolicyCategory
    let initialProvider: String
    let initialPolicyNumber: String
    let initialCost: Decimal
    let initialCostFrequency: CostFrequency
    let initialRenewalDate: Date
    let initialNotes: String
    let initialReminderThirtyDays: Bool
    let initialReminderFourteenDays: Bool
    let initialReminderThreeDays: Bool
    let initialReminderRenewalDay: Bool

    // Type state - utilizing inline initialization like AddPolicyView
    @State non-public var identify = ""
    @State non-public var class: PolicyCategory = .insurance coverage
    @State non-public var supplier = ""
    @State non-public var policyNumber = ""
    @State non-public var price: Decimal = 0
    @State non-public var costString = ""
    @State non-public var costFrequency: CostFrequency = .yearly
    @State non-public var renewalDate = Date()
    @State non-public var notes = ""

    // Reminder schedule
    @State non-public var reminderThirtyDays = true
    @State non-public var reminderFourteenDays = true
    @State non-public var reminderThreeDays = true
    @State non-public var reminderRenewalDay = true

    // Observe if we have loaded preliminary values
    @State non-public var hasLoadedInitialValues = false

    // Attachments - use light-weight summaries for show, monitor adjustments individually
    @State non-public var attachmentSummaries: [AttachmentSummary] = []
    @State non-public var newAttachments: [Attachment] = []  // Newly added attachments (with knowledge)
    @State non-public var deletedAttachmentIDs: Set<UUID> = []  // IDs of current attachments to delete
    @State non-public var attachmentsLoaded = false
    @State non-public var selectedPhotoItems: [PhotosPickerItem] = []
    @State non-public var showingDocumentScanner = false
    @State non-public var showingFilePicker = false

    @State non-public var showingValidationError = false
    @State non-public var validationErrorMessage = ""

    // MARK: - Subscription-specific Labels
    non-public var isSubscription: Bool {
        class == .subscription
    }

    non-public var nameFieldLabel: String {
        isSubscription ? "Subscription Title" : "Title"
    }

    non-public var providerFieldLabel: String {
        isSubscription ? "Service" : "Supplier"
    }

    non-public var referenceFieldLabel: String {
        isSubscription ? "Account ID (elective)" : "Reference Quantity"
    }

    non-public var dateFieldLabel: String {
        isSubscription ? "Subsequent Billing Date" : "Renewal Date"
    }

    non-public var basicInfoSectionHeader: String {
        isSubscription ? "Subscription Particulars" : "Fundamental Info"
    }

    non-public var dateSectionHeader: String {
        isSubscription ? "Billing" : "Renewal"
    }

    non-public var reminderFooterText: String {
        isSubscription
            ? "You may obtain notifications at 9:00 AM earlier than your billing date."
            : "You may obtain notifications at 9:00 AM on lately."
    }

    var physique: some View {
        // Match AddPolicyView construction precisely
        NavigationStack {
            Type {
                // Fundamental Data Part - minimal check
                Part {
                    TextField(nameFieldLabel, textual content: $identify)
                } header: {
                    Textual content(basicInfoSectionHeader)
                }
            }
            .navigationTitle("Edit File")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") {
                        dismiss()
                    }
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button("Save") {
                        saveChanges()
                    }
                    .disabled(identify.isEmpty)
                }
            }
            .alert("Validation Error", isPresented: $showingValidationError) {
                Button("OK") { }
            } message: {
                Textual content(validationErrorMessage)
            }
            .onAppear {
                // Load preliminary values solely as soon as
                if !hasLoadedInitialValues {
                    identify = initialName
                    class = initialCategory
                    supplier = initialProvider
                    policyNumber = initialPolicyNumber
                    price = initialCost
                    costString = "(initialCost)"
                    costFrequency = initialCostFrequency
                    renewalDate = initialRenewalDate
                    notes = initialNotes
                    reminderThirtyDays = initialReminderThirtyDays
                    reminderFourteenDays = initialReminderFourteenDays
                    reminderThreeDays = initialReminderThreeDays
                    reminderRenewalDay = initialReminderRenewalDay
                    hasLoadedInitialValues = true
                }
            }
        }
        /* TEMPORARILY DISABLED - restore after keyboard check
        .sheet(isPresented: $showingDocumentScanner) {
            DocumentScannerView { pictures in
                processScannedImages(pictures)
            }
        }
        .sheet(isPresented: $showingFilePicker) {
            DocumentPickerView { urls in
                processSelectedFiles(urls)
            }
        }
        .onChange(of: selectedPhotoItems) { _, newItems in
            processSelectedPhotos(newItems)
        }
        .activity {
            // Load attachments in background to keep away from blocking UI
            await loadAttachments()
        }
        */
    }

    // Load attachment METADATA solely (not binary knowledge) to keep away from blocking foremost thread
    non-public func loadAttachments() async {
        guard !attachmentsLoaded else { return }
        let begin = CFAbsoluteTimeGetCurrent()
        print("⏱️ loadAttachments: beginning...")

        // Use a background context to keep away from blocking foremost thread
        let container = modelContext.container
        let policyIDCopy = policyID

        // Fetch uncooked metadata as tuples (Sendable) from background
        let metadata: [(UUID, String, String)] = await Process.indifferent {
            let bgStart = CFAbsoluteTimeGetCurrent()
            let backgroundContext = ModelContext(container)
            guard let coverage = backgroundContext.mannequin(for: policyIDCopy) as? PolicyItem else {
                return []
            }
            // Solely entry metadata properties, NOT the info property
            let consequence = coverage.safeAttachments.map { ($0.id, $0.filename, $0.mimeType) }
            print("⏱️ loadAttachments background activity took (CFAbsoluteTimeGetCurrent() - bgStart)s")
            return consequence
        }.worth

        // Create summaries on foremost actor
        attachmentSummaries = metadata.map {
            AttachmentSummary(id: $0.0, filename: $0.1, mimeType: $0.2, isExisting: true)
        }
        attachmentsLoaded = true
        print("⏱️ loadAttachments: TOTAL took (CFAbsoluteTimeGetCurrent() - begin)s")
    }

    // MARK: - Save Modifications
    non-public func saveChanges() {
        guard !identify.trimmingCharacters(in: .whitespaces).isEmpty else {
            validationErrorMessage = "Please enter a reputation."
            showingValidationError = true
            return
        }

        // Fetch the coverage by ID
        guard let coverage = modelContext.mannequin(for: policyID) as? PolicyItem else {
            validationErrorMessage = "Couldn't discover report to replace."
            showingValidationError = true
            return
        }

        coverage.identify = identify.trimmingCharacters(in: .whitespaces)
        coverage.class = class
        coverage.supplier = supplier.trimmingCharacters(in: .whitespaces)
        coverage.policyNumber = policyNumber.trimmingCharacters(in: .whitespaces)
        coverage.price = price
        coverage.costFrequency = costFrequency
        coverage.renewalDate = renewalDate
        coverage.notes = notes.trimmingCharacters(in: .whitespaces)
        coverage.updatedAt = Date()

        coverage.reminderSchedule = ReminderSchedule(
            thirtyDays: reminderThirtyDays,
            fourteenDays: reminderFourteenDays,
            threeDays: reminderThreeDays,
            renewalDay: reminderRenewalDay
        )

        // Solely modify attachments that modified (not rewriting the whole lot)
        // 1. Take away deleted attachments
        if !deletedAttachmentIDs.isEmpty {
            coverage.safeAttachments.removeAll { deletedAttachmentIDs.comprises($0.id) }
        }

        // 2. Add new attachments
        for attachment in newAttachments {
            coverage.safeAttachments.append(attachment)
        }

        // Reschedule notifications
        Process {
            await NotificationManager.shared.scheduleNotifications(for: coverage)
        }

        dismiss()
    }

    // MARK: - Attachment Dealing with
    non-public func removeAttachment(_ abstract: AttachmentSummary) {
        attachmentSummaries.removeAll { $0.id == abstract.id }
        if abstract.isExisting {
            // Mark current attachment for deletion on save
            deletedAttachmentIDs.insert(abstract.id)
        } else {
            // Take away newly added attachment
            newAttachments.removeAll { $0.id == abstract.id }
        }
    }

    non-public func processScannedImages(_ pictures: [UIImage]) {
        for (index, picture) in pictures.enumerated() {
            if let knowledge = picture.jpegData(compressionQuality: 0.8) {
                let id = UUID()
                let filename = "scan_(attachmentSummaries.rely + index + 1).jpg"
                let mimeType = "picture/jpeg"

                // Add to newAttachments (with knowledge) for saving
                let attachment = Attachment(filename: filename, knowledge: knowledge, mimeType: mimeType)
                attachment.id = id
                newAttachments.append(attachment)

                // Add abstract for show
                attachmentSummaries.append(AttachmentSummary(id: id, filename: filename, mimeType: mimeType))
            }
        }
    }

    non-public func processSelectedPhotos(_ gadgets: [PhotosPickerItem]) {
        for merchandise in gadgets {
            Process {
                if let knowledge = strive? await merchandise.loadTransferable(kind: Knowledge.self) {
                    await MainActor.run {
                        let id = UUID()
                        let filename = "photo_(attachmentSummaries.rely + 1).jpg"
                        let mimeType = "picture/jpeg"

                        // Add to newAttachments (with knowledge) for saving
                        let attachment = Attachment(filename: filename, knowledge: knowledge, mimeType: mimeType)
                        attachment.id = id
                        newAttachments.append(attachment)

                        // Add abstract for show
                        attachmentSummaries.append(AttachmentSummary(id: id, filename: filename, mimeType: mimeType))
                    }
                }
            }
        }
        selectedPhotoItems = []
    }

    non-public func processSelectedFiles(_ urls: [URL]) {
        for url in urls {
            guard url.startAccessingSecurityScopedResource() else { proceed }
            defer { url.stopAccessingSecurityScopedResource() }

            if let knowledge = strive? Knowledge(contentsOf: url) {
                let id = UUID()
                let filename = url.lastPathComponent
                let mimeType = url.pathExtension.lowercased() == "pdf" ? "software/pdf" : "picture/jpeg"

                // Add to newAttachments (with knowledge) for saving
                let attachment = Attachment(filename: filename, knowledge: knowledge, mimeType: mimeType)
                attachment.id = id
                newAttachments.append(attachment)

                // Add abstract for show
                attachmentSummaries.append(AttachmentSummary(id: id, filename: filename, mimeType: mimeType))
            }
        }
    }
}

#Preview {
    EditPolicyView(coverage: PolicyItem(
        identify: "Check Coverage",
        class: .insurance coverage,
        supplier: "Check Supplier",
        renewalDate: Date()
    ))
    .modelContainer(for: PolicyItem.self, inMemory: true)
    .environmentObject(AppSettings.shared)
}

Additionally A screenshot of the view operating on an iPhone 17 Professional Max: Issues with keyboard responsiveness in edit sheet [closed]

I’m certain I’m doing one thing intensely silly and would be thankful for assist from the group on this.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

[td_block_social_counter facebook="tagdiv" twitter="tagdivofficial" youtube="tagdiv" style="style8 td-social-boxed td-social-font-icons" tdc_css="eyJhbGwiOnsibWFyZ2luLWJvdHRvbSI6IjM4IiwiZGlzcGxheSI6IiJ9LCJwb3J0cmFpdCI6eyJtYXJnaW4tYm90dG9tIjoiMzAiLCJkaXNwbGF5IjoiIn0sInBvcnRyYWl0X21heF93aWR0aCI6MTAxOCwicG9ydHJhaXRfbWluX3dpZHRoIjo3Njh9" custom_title="Stay Connected" block_template_id="td_block_template_8" f_header_font_family="712" f_header_font_transform="uppercase" f_header_font_weight="500" f_header_font_size="17" border_color="#dd3333"]
- Advertisement -spot_img

Latest Articles