Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
77: NSPredicate for Filtering
You've already spent a lot of time using the .filter { ... } method on Swift arrays. For most of your daily work, that's exactly what you should use. It's type-safe, fast, and idiomatic. But as you move deeper into the Apple ecosystem—especially when you start hitting Core Data or working with NSArray—you're going to run into NSPredicate.
Think of NSPredicate as a way to define a logical rule as a piece of data. Instead of writing a closure, you're essentially writing a "query" that can be passed around and evaluated. It's incredibly powerful for complex filtering logic that might change at runtime.
Defining our Task model
Let's build a simple task filtering system. I want to be able to filter a list of tasks based on whether they are completed and whether the title contains a specific keyword. First, let's set up our data. I'll use a class here because NSPredicate relies on Objective-C dynamism to inspect properties at runtime.
class TodoTask: NSObject {
@objc var title: String
@objc var isCompleted: Bool
@objc var priority: Int
init(title: String, isCompleted: Bool, priority: Int) {
self.title = title
self.isCompleted = isCompleted
self.priority = priority
}
}
let tasks = [
TodoTask(title: "Buy milk", isCompleted: false, priority: 1),
TodoTask(title: "Finish Swift lesson", isCompleted: true, priority: 3),
TodoTask(title: "Call the bank", isCompleted: false, priority: 2),
TodoTask(title: "Clean the kitchen", isCompleted: false, priority: 1)
]
Filtering by simple equality
Now, let's say I only want the tasks that aren't finished yet. With a standard Swift filter, I'd just check !$0.isCompleted. With NSPredicate, I use a format string. It looks a bit like SQL, which some people love and others hate.
let pendingPredicate = NSPredicate(format: "isCompleted == %@", NSNumber(value: false))
let pendingTasks = (tasks as NSArray).filtered(using: pendingPredicate) as! [TodoTask]
// result: Buy milk, Call the bank, Clean the kitchen
Notice the %@. That's a placeholder. Never hardcode values directly into the format string if you can avoid it; using placeholders prevents "injection" style bugs and handles type conversion more gracefully. I also cast the Swift array to NSArray because the filtered(using:) method lives there.
Adding some fuzzy search logic
In a real app, users usually type into a search bar. I want to find tasks that contain a certain word, regardless of whether they used capital letters or not. This is where NSPredicate actually starts to outshine basic Swift filters because of operators like CONTAINS[cd].
The [c] means case-insensitive, and the [d] means diacritic-insensitive (so "é" matches "e").
let searchText = "MILK"
let searchPredicate = NSPredicate(format: "title CONTAINS[cd] %@", searchText)
let searchResults = (tasks as NSArray).filtered(using: searchPredicate) as! [TodoTask]
// result: Buy milk
Wait, why isn't this filtering?
Here is where I usually trip up when I'm moving quickly. I tried to add a priority filter, but I wrote it like this:
// MY MISTAKE:
let priorityPredicate = NSPredicate(format: "priority == 2")
// This actually works for constants, but wait...
// what if I use a variable?
let minPriority = 2
let badPredicate = NSPredicate(format: "priority == minPriority")
I spent five minutes wondering why badPredicate wasn't returning anything. The problem is that NSPredicate thinks "minPriority" is the name of a property on the TodoTask object, not a reference to the Swift variable minPriority. It's searching for a property that doesn't exist.
To fix this, I have to use the %@ placeholder to pass the actual value of the variable into the predicate.
// THE FIX:
let minPriority = 2
let fixedPredicate = NSPredicate(format: "priority == %@", NSNumber(value: minPriority))
Combining rules with Compound Predicates
Most of the time, you don't just want one filter; you want several. You might want "incomplete tasks" AND "high priority". Instead of nesting filters, we can use NSCompoundPredicate.
let isPending = NSPredicate(format: "isCompleted == %@", NSNumber(value: false))
let isHighPriority = NSPredicate(format: "priority > %@", NSNumber(value: 1))
let combinedPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [isPending, isHighPriority])
let urgentTasks = (tasks as NSArray).filtered(using: combinedPredicate) as! [TodoTask]
// result: Call the bank
It's a bit more verbose than Swift's &&, but the beauty of this approach is that you can dynamically build an array of predicates based on which checkboxes a user has clicked in your UI, and then smash them together into one single query.
There are no comments for now.