Home Blog

ios – Greatest method to implement Generic protocol in Swift?


I’m following VIPER and I wish to optimize my interactor extra for reusability. I’ve an technique in interactor known as cancelRequest() which might be invoked from presenter to cancel ongoing community calls. Most of my interactors have this technique and I’m making an attempt to separate it out as a generic technique utilizing protocols.

protocol CancellableService {
    var community: NetworkManager { get }
    func cancelRequest()
}

extension CancellableService {
    func cancelRequest() {
        community.cancel()
    }
}

protocol AnyFeatureService: AnyObject, CancellableService {
    func fetch()
    func deleteItem(with id: Int)
}

class MyFeatureService: AnyFeatureService {
    // Implementation
}

protocol CancellableInteractor {
    associatedtype ServiceType: CancellableService
    var service: ServiceType! { get set }
    func cancelRequest()
}

extension CancellableInteractor {
    func cancelRequest() {
        service.cancelRequest()
    }
}

protocol MyFeatureInteractable: AnyObject, CancellableInteractor {
    var presenter: MyFeaturePresentableOutput? { get set }
    
    func fetchList()
    func deleteItem(with id: Int)
}

Downside is available in MyFeatureInteractor implementation. I don’t know learn how to declare service, that conforms to MyFeatureService (which additionally conforms to CancellableService) within the interactor.

With under implementation, I get

Kind ‘MyFeatureInteractor’ doesn’t conform to protocol ‘CancellableInteractor’. Presumably supposed match ‘MyFeatureInteractor.ServiceType’ (aka ‘AnyFeatureService’) doesn’t conform to ‘CancellableService’

class MyFeatureInteractor: MyFeatureInteractable {
    typealias ServiceType = AnyFeatureService
    weak var presenter: MyFeaturePresentableOutput?
    var service: AnyFeatureService!

    // func implementation
}

additionally tried declaring MyFeatureInteractable as under, which complains

Kind ‘MyFeatureInteractor’ doesn’t conform to protocol ‘CancellableInteractor’. Candidate can’t infer ‘ServiceType’ = ‘any AnyFeatureService’ as a result of ‘any AnyFeatureService’ shouldn’t be a nominal kind and so cannot conform to ‘CancellableService’`

protocol MyFeatureInteractable: AnyObject, CancellableInteractor the place ServiceType: AnyFeatureService {
    // Declarations
}

What’s the clear method to do it?

ios – Listing will not replace when merchandise is deleted


I’ve two selectable lists, the primary is populated with Class mannequin objects and the second with Subcategory mannequin objects (that are taken from chosen Class worth of the primary checklist).

Once I delete an merchandise from the primary checklist, the second checklist doesn’t replace. Here’s a video that exhibits what I imply: https://youtube.com/shorts/VJCe_UK0TNA

I’ve coded the selectable lists in order that the chosen object is retrieved from a computed property binding that returns the item with the latest chosen date:

personal var selectedCategoryBinding: Binding
{
    return Binding(get: { self.classes.max(by: { ($0.lastSelectedDate) < ($1.lastSelectedDate) })}, set:{_ in})
}

personal var selectedSubcategoriesBinding: Binding<[Subcategory1]?>
{
    Binding<[Subcategory1]?>(get: { self.selectedCategoryBinding.wrappedValue?.subcategories }, set: { self.selectedCategoryBinding.wrappedValue?.subcategories = $0 })
} 

I concern that this can be the place the issue lies, however I am uncertain the right way to repair it. Beneath is the whole code. Any assist could be appreciated.

import SwiftUI
import SwiftData

protocol SelectableListItem: Equatable, Identifiable, PersistentModel {
    var identify: String { get set }
    var lastSelectedDate: Date { get set }
}

@Mannequin
closing class Project1 {
    var identify: String = ""
    @Relationship(deleteRule: .cascade, inverse: Category1.undertaking) var classes: [Category1]?
    
    init(identify: String) {
        self.identify = identify
    }
}

@Mannequin
closing class Category1: SelectableListItem {
    var identify: String = ""
    @Relationship(deleteRule: .cascade, inverse: Subcategory1.class) var subcategories: [Subcategory1]?
    var undertaking: Project1?
    var lastSelectedDate: Date = Date.now
    
    init(identify: String) {
        self.identify = identify
    }
}

@Mannequin
closing class Subcategory1: SelectableListItem {
    var identify: String = ""
    var class: Category1?
    var lastSelectedDate: Date = Date.now
    
    init(identify: String) {
        self.identify = identify
    }
}

struct HomeView: View {
    @Setting(.modelContext) personal var modelContext
    @Question personal var queriedProjects: [Project1]

    var physique: some View {
        NavigationStack {
            VStack {
                Listing {
                    ForEach(queriedProjects) { undertaking in
                        if let categoriesBinding = Binding(
                            Binding<[Category1]?>(
                                get: { undertaking.classes },
                                set: { undertaking.classes = $0 }
                            )
                        ) {
                            NavigationLink(
                                vacation spot: ProjectView1(undertaking: undertaking, classes: categoriesBinding),
                                label: { Textual content(undertaking.identify) }
                            )
                        }
                    }
                }
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            let undertaking = Project1(identify: "Undertaking 1")
                            
                            let subcategory1 = Subcategory1(identify: "Subcategory 1")
                            let subcategory2 = Subcategory1(identify: "Subcategory 2")
                            let subcategory3 = Subcategory1(identify: "Subcategory 3")
                            let subcategory4 = Subcategory1(identify: "Subcategory 4")
                            
                            let category1 = Category1(identify: "Class 1")
                            category1.subcategories = [subcategory1, subcategory2]
                            
                            let category2 = Category1(identify: "Class 2")
                            category2.subcategories = [subcategory3, subcategory4]
                            
                            undertaking.classes = [category1, category2]
                            modelContext.insert(undertaking)
                        } label: {
                            Label("Plus", systemImage: "plus")
                        }
                    }
                }
            }
            .overlay {
                if queriedProjects.isEmpty {
                    Textual content("Faucet the + button to create a brand new undertaking")
                }
            }
            .process {
                do {
                    attempt modelContext.delete(mannequin: Project1.self)
                } catch {
                    fatalError(error.localizedDescription)
                }
            }
        }
        .navigationSplitViewStyle(.balanced)
    }
}

struct ProjectView1: View {
    @Bindable var undertaking: Project1
    @Binding var classes: [Category1]
    
    @Setting(.editMode) personal var editMode
    @Setting(.modelContext) personal var modelContext
    
    @State personal var columnVisibility: NavigationSplitViewVisibility = .all

    personal var selectedCategoryBinding: Binding {
        Binding(
            get: { classes.max(by: { $0.lastSelectedDate < $1.lastSelectedDate }) },
            set: { _ in }
        )
    }
    
    personal var selectedSubcategoriesBinding: Binding<[Subcategory1]?> {
        Binding<[Subcategory1]?>(
            get: { selectedCategoryBinding.wrappedValue?.subcategories },
            set: { selectedCategoryBinding.wrappedValue?.subcategories = $0 }
        )
    }
    
    var physique: some View {
        NavigationSplitView(columnVisibility: $columnVisibility) {
            SelectableList(gadgets: $classes) { class in
                class.undertaking = nil
            }
            
            if !classes.isEmpty {
                if let subcategoriesBinding = Binding(selectedSubcategoriesBinding) {
                    SelectableList(gadgets: subcategoriesBinding) { subcategory in
                        subcategory.class = nil
                    }
                }
            }
        } element: {
            EmptyView()
        }
    }
}

struct SelectableList: View {
    @Binding var gadgets: [T]
    personal(set) var deleteItem: (T) -> Void
    
    @Setting(.modelContext) personal var modelContext
    @Question personal var queriedItems: [T]
    
    personal var filteredAndSortedItems: [T] {
        queriedItems
            .filter { gadgets.incorporates($0) }
            .sorted { $0.identify.localizedCaseInsensitiveCompare($1.identify) == .orderedAscending }
    }
    
    personal var selectedItem: T? {
        filteredAndSortedItems.max(by: { $0.lastSelectedDate < $1.lastSelectedDate })
    }
    
    var physique: some View {
        Listing {
            ForEach(filteredAndSortedItems) { merchandise in
                HStack {
                    Button {
                        if selectedItem != merchandise {
                            merchandise.lastSelectedDate = .now
                        }
                    } label: {
                        Textual content(merchandise.identify)
                    }
                    .buttonStyle(PlainButtonStyle())
                                                                                                
                    if selectedItem == merchandise {
                        Picture(systemName: "checkmark")
                    }
                    
                    Spacer()
                    
                    Button {
                        deleteItem(merchandise)
                        modelContext.delete(merchandise)
                    } label: {
                        Picture(systemName: "trash")
                    }
                }
            }
        }
    }
}

webkit – iOS 26 internet web page with script is terminated on customized scheme WKURLSchemeHandler


One thing has modified in iOS 26 (examined on beta 4) and now if customized scheme is used and internet web page accommodates script aspect then WebKit is terminated with following log:

0x1130bc170 - [PID=47858] WebProcessProxy::didClose: (internet course of 0 crash)
0x1130bc170 - [PID=47858] WebProcessProxy::processDidTerminateOrFailedToLaunch: motive=Crash

Steps to breed:

  1. Create WKWebView with customized configuration and customized url scheme handler
  2. Load HTML with script aspect inside that references to a different file

Anticipated consequence: WKWebView is loaded

Precise consequence: WKWebView is terminated

Code instance:

last class CustomSchemeViewController: UIViewController {
    override func viewDidLoad() {
        tremendous.viewDidLoad()

        let sampleConfiguration = WKWebViewConfiguration()
        sampleConfiguration.setURLSchemeHandler(
            SampleURLSchemeHandler(),
            forURLScheme: "pattern"
        )

        let webView = WKWebView(body: view.bounds, configuration: sampleConfiguration)
        webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(webView)

        webView.navigationDelegate = self

        webView.load(URLRequest(url: URL(string: "pattern://pages/pattern.html")!))
    }
}

extension CustomSchemeViewController: WKNavigationDelegate {
    func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
        print("webViewWebContentProcessDidTerminate")
    }
}

last class SampleURLSchemeHandler: NSObject, WKURLSchemeHandler {
    personal func submit(_ physique: String, mimeType: String, urlSchemeTask: WKURLSchemeTask) {
        let physique = Information(physique.utf8)

        let response = URLResponse(
            url: urlSchemeTask.request.url!,
            mimeType: mimeType,
            expectedContentLength: physique.depend,
            textEncodingName: nil
        )

        urlSchemeTask.didReceive(response)
        urlSchemeTask.didReceive(physique)
        urlSchemeTask.didFinish()
    }

    func webView(_ webView: WKWebView, begin urlSchemeTask: WKURLSchemeTask) {
        change urlSchemeTask.request.url?.lastPathComponent {
        case "pattern.html":
            submit("""