A simple way to build Settings-style screens for iOS apps.
JMStaticContentTableViewController allows you to easily and simply display "static content" like iOS Settings, About screens, Login forms, or any screen that displays or collects information in a table format.
Originally built with blocks in Objective-C, now fully rewritten in Swift with both UIKit and SwiftUI support.
Features
- Declarative API - Build table views with simple, readable code
- UIKit Support -
JMStaticContentTableViewControllersubclass - SwiftUI Support -
JMStaticContentListview component - Built-in Cell Types - Text, toggle, value display cells
- Section Management - Add, insert, remove sections with animations
- Custom Cells - Use any
UITableViewCellsubclass - Backward Compatible - Original Objective-C API still works
- Thread-safe - All operations properly synchronize with the table view
Requirements
- iOS 15.0+ / macOS 12.0+ / tvOS 15.0+
- Swift 5.9+
- Xcode 15.0+
For older iOS versions (5.0-14.x), use the legacy Objective-C implementation included in this repo.
Installation
Swift Package Manager (Recommended)
Add JMStaticContentTableViewController to your project via SPM:
dependencies: [ .package(url: "https://github.com/jakemarsh/JMStaticContentTableViewController.git", from: "2.0.0") ]
Or in Xcode: File → Add Package Dependencies → Enter the repository URL.
CocoaPods (Legacy)
pod 'JMStaticContentTableViewController'
Quick Start
Swift (UIKit)
import JMStaticContentTableViewController class SettingsViewController: JMStaticContentTableViewController { override func viewDidLoad() { super.viewDidLoad() // Add an Account section addSection { section, _ in section.headerTitle = "Account" // Navigation cell section.addCell(.text("Profile", onSelect: { [weak self] _ in self?.navigationController?.pushViewController(ProfileViewController(), animated: true) })) // Toggle cell section.addCell(.toggle("Notifications", isOn: true) { isOn in UserDefaults.standard.set(isOn, forKey: "notifications") }) // Value display cell section.addCell(.value("Version", value: "2.0.0")) } // Add an About section addSection { section, _ in section.headerTitle = "About" section.footerTitle = "Thanks for using our app!" section.addCell(.text("Terms of Service", onSelect: { _ in // Open terms })) } } }
SwiftUI
import JMStaticContentTableViewController struct SettingsView: View { @State private var notificationsEnabled = true var body: some View { NavigationView { JMStaticContentList { JMStaticContentListSection(header: "Account") { JMStaticContentListRow("Profile", systemImage: "person.circle") { ProfileView() } JMStaticContentListToggle("Notifications", isOn: $notificationsEnabled) JMStaticContentListRow("Version", value: "2.0.0") } JMStaticContentListSection(header: "About", footer: "Thanks for using our app!") { JMStaticContentListRow("Terms of Service") { // Handle tap } } } .navigationTitle("Settings") } } }
Objective-C (Legacy)
#import "JMStaticContentTableViewController.h" - (void)viewDidLoad { [super viewDidLoad]; [self addSection:^(JMStaticContentTableViewSection *section, NSUInteger sectionIndex) { section.headerTitle = @"Account"; [section addCell:^(JMStaticContentTableViewCell *staticContentCell, UITableViewCell *cell, NSIndexPath *indexPath) { staticContentCell.cellStyle = UITableViewCellStyleValue1; cell.textLabel.text = @"Wi-Fi"; cell.detailTextLabel.text = @"Connected"; } whenSelected:^(NSIndexPath *indexPath) { [self.navigationController pushViewController:[[WifiViewController alloc] init] animated:YES]; }]; }]; }
Cell Types
Text Cell
// Simple text section.addCell(.text("Label")) // With subtitle section.addCell(.text("Label", detailText: "Subtitle")) // With image section.addCell(.text("Label", image: UIImage(systemName: "star"))) // With selection handler section.addCell(.text("Label", onSelect: { indexPath in // Handle selection }))
Toggle Cell
section.addCell(.toggle("Enable Feature", isOn: currentValue) { newValue in // Handle toggle change })
Value Cell
section.addCell(.value("Version", value: "2.0.0"))
Custom Cell
let cell = JMStaticContentCell( reuseIdentifier: "CustomCell", cellClass: MyCustomCell.self, height: 80 ) cell.configureBlock = { cellConfig, tableViewCell, indexPath in guard let customCell = tableViewCell as? MyCustomCell else { return } customCell.configure(with: myData) } cell.whenSelectedBlock = { indexPath in // Handle selection } section.addCell(cell)
Section Management
// Add a section addSection { section, index in section.headerTitle = "New Section" // Add cells... } // Insert a section at a specific index insertSection({ section, index in // Configure section... }, at: 1, animated: true) // Remove a section removeSection(at: 0, animated: true) // Remove all sections removeAllSections() // Reload a section reloadSection(at: 0, animated: true)
Migration from v1.x (Objective-C)
The Swift rewrite maintains API compatibility. Most Objective-C code will continue to work. For Swift projects:
| Old API (Obj-C) | New API (Swift) |
|---|---|
[self addSection:^...] |
addSection { section, index in ... } |
section.headerTitle = @"Title" |
section.headerTitle = "Title" |
[section addCell:^... whenSelected:^...] |
section.addCell(.text("Label", onSelect: { ... })) |
[self removeAllSections] |
removeAllSections() |
How It Works
Images can be in three states when you add them:
- Configure Block - Called each time the cell is displayed, configure the UITableViewCell here
- When Selected Block - Called when the user taps the cell, handle navigation or actions here
- Automatic Cell Reuse - Cells are automatically dequeued and reused based on the reuse identifier
Demo App
The repository includes a Settings example app demonstrating typical usage patterns like:
- Wi-Fi network selection with animated row insertion
- Toggle controls for settings
- Navigation between screens
- Section headers and footers
License
JMStaticContentTableViewController is available under the MIT license. See the LICENSE file for details.
Author
Jake Marsh (@jakemarsh)
Originally created in 2011, rewritten in Swift in 2024.