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
172: IndexPath and Range Types
If you've spent any time working with UITableView or UICollectionView, you've encountered IndexPath. On the surface, it seems like a simple wrapper around an integer—essentially just a fancy way of saying "item 5 in section 0." But as your data structures get more complex, especially when you start performing batch updates or slicing data, the way you handle these paths and the ranges they imply can either make your code elegant or turn it into a debugging nightmare.
The temptation to manual-map indices
Let's say you're building a messaging app. You have a list of messages in a single section, and you want to implement a "Delete Selected" feature. The naive approach is to take the array of selected IndexPath objects and loop through them to remove the messages from your data source. It looks something like this:
var messages = ["Hello!", "How are you?", "I'm great!", "Swift is fun!", "Bye!"]
let selectedPaths = [IndexPath(row: 1, section: 0), IndexPath(row: 2, section: 0)]
for path in selectedPaths {
messages.remove(at: path.row)
}
I've seen this countless times in code reviews. At first glance, it seems logical. But there's a massive trap here: index shifting. The moment you remove the item at index 1, "I'm great!" (which was at index 2) shifts up to index 1. When the loop moves to the next IndexPath and tries to remove index 2, it's actually removing "Swift is fun!" instead. If your selection list is long enough, you'll eventually hit an Index out of range crash.
Bridging IndexPath to Range types
The professional way to handle this isn't to loop and delete, but to define a Range. In Swift, ranges aren't just for for-in loops; they are first-class citizens used for slicing collections. When you're dealing with a contiguous block of items, you want to move away from individual IndexPath objects and toward Range or ClosedRange.
If you know you're deleting everything from index 1 to 2, you should use a half-open range (..<) or a closed range (...). For a data source, removeSubrange(_:) is your best friend because it handles the memory shift in one go, rather than forcing the array to re-index itself multiple times.
let start = selectedPaths.first!.row
let end = selectedPaths.last!.row
// We use a ClosedRange here because 'end' is inclusive
let rangeToRemove = start...end
messages.removeSubrange(rangeToRemove)
This is significantly more performant. Instead of $O(n^2)$ complexity where you shift the remaining elements for every single deletion, you're doing a single memory move. I generally prefer Range (half-open) for most Swift API work because it aligns with how Array.count works, but ClosedRange is often more intuitive when you're translating a "start" and "end" IndexPath from a UI selection.
When the structure gets deeper
Now, the real power of IndexPath comes when you have multiple sections. A Range only works on a linear sequence. If you have messages grouped by date (Section 0: Monday, Section 1: Tuesday), you can't just create one Range to delete messages across both days. This is where you have to be careful.
You'll need to group your IndexPaths by section first, then create a Range for each section. If you try to flatten these into a single range, you'll end up deleting data from the wrong sections or crashing the app. Remember: IndexPath is the coordinate, but Range is the span. You use the coordinates to determine the boundaries, and the span to execute the operation.
📋 Practical Task
Implementing a Batch Message Archive
You are working on a chat application. You have a data source consisting of an array of strings called chatHistory. You need to implement a function that archives a contiguous block of messages based on a starting IndexPath and an ending IndexPath.
Requirements:
- Create a function
archiveMessages(from start: IndexPath, to end: IndexPath). - The function should use a
ClosedRangeto identify the items to be removed. - Use
removeSubrange(_:)to remove these messages from thechatHistoryarray in a single operation. - Add a print statement that shows the remaining messages to verify that the correct range was deleted.
var chatHistory = ["Msg 1", "Msg 2", "Msg 3", "Msg 4", "Msg 5", "Msg 6"]
func archiveMessages(from start: IndexPath, to end: IndexPath) {
// Your code here
}
// Test case: Archive from Msg 2 to Msg 4 (indices 1 through 3)
archiveMessages(from: IndexPath(row: 1, section: 0), to: IndexPath(row: 3, section: 0))
There are no comments for now.