Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
297
ios-application-dev/references/uikit-components.md
Normal file
297
ios-application-dev/references/uikit-components.md
Normal file
@@ -0,0 +1,297 @@
|
||||
# UIKit Components
|
||||
|
||||
Common UIKit components guide covering UIStackView, buttons, alerts, search, and context menus.
|
||||
|
||||
## UIStackView
|
||||
|
||||
Stack views simplify auto layout for linear arrangements:
|
||||
|
||||
```swift
|
||||
class FormViewController: UIViewController {
|
||||
private let mainStack = UIStackView()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
mainStack.axis = .vertical
|
||||
mainStack.spacing = 16
|
||||
mainStack.alignment = .fill
|
||||
mainStack.distribution = .fill
|
||||
|
||||
view.addSubview(mainStack)
|
||||
mainStack.snp.makeConstraints { make in
|
||||
make.top.equalTo(view.safeAreaLayoutGuide).offset(20)
|
||||
make.leading.trailing.equalToSuperview().inset(16)
|
||||
}
|
||||
|
||||
let headerStack = UIStackView()
|
||||
headerStack.axis = .horizontal
|
||||
headerStack.spacing = 12
|
||||
headerStack.alignment = .center
|
||||
|
||||
let avatarView = UIImageView()
|
||||
avatarView.snp.makeConstraints { make in
|
||||
make.size.equalTo(48)
|
||||
}
|
||||
|
||||
let labelStack = UIStackView()
|
||||
labelStack.axis = .vertical
|
||||
labelStack.spacing = 4
|
||||
labelStack.addArrangedSubview(titleLabel)
|
||||
labelStack.addArrangedSubview(subtitleLabel)
|
||||
|
||||
headerStack.addArrangedSubview(avatarView)
|
||||
headerStack.addArrangedSubview(labelStack)
|
||||
|
||||
mainStack.addArrangedSubview(headerStack)
|
||||
mainStack.addArrangedSubview(contentView)
|
||||
mainStack.addArrangedSubview(actionButton)
|
||||
|
||||
mainStack.setCustomSpacing(24, after: headerStack)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### StackView Properties
|
||||
|
||||
| Property | Options | Usage |
|
||||
|----------|---------|-------|
|
||||
| `axis` | `.horizontal`, `.vertical` | Layout direction |
|
||||
| `distribution` | `.fill`, `.fillEqually`, `.fillProportionally`, `.equalSpacing`, `.equalCentering` | Space distribution |
|
||||
| `alignment` | `.fill`, `.leading`, `.center`, `.trailing` | Cross-axis alignment |
|
||||
| `spacing` | CGFloat | Uniform spacing |
|
||||
| `setCustomSpacing(_:after:)` | - | Variable spacing |
|
||||
|
||||
## UIButton.Configuration (iOS 15+)
|
||||
|
||||
```swift
|
||||
let primaryButton = UIButton(type: .system)
|
||||
primaryButton.configuration = .filled()
|
||||
primaryButton.setTitle("Continue", for: .normal)
|
||||
|
||||
let secondaryButton = UIButton(type: .system)
|
||||
secondaryButton.configuration = .tinted()
|
||||
secondaryButton.setTitle("Save for Later", for: .normal)
|
||||
|
||||
let destructiveButton = UIButton(type: .system)
|
||||
destructiveButton.configuration = .plain()
|
||||
destructiveButton.setTitle("Remove", for: .normal)
|
||||
destructiveButton.tintColor = .systemRed
|
||||
```
|
||||
|
||||
### Custom Button Configuration
|
||||
|
||||
```swift
|
||||
var config = UIButton.Configuration.filled()
|
||||
config.title = "Add to Cart"
|
||||
config.image = UIImage(systemName: "cart.badge.plus")
|
||||
config.imagePadding = 8
|
||||
config.cornerStyle = .capsule
|
||||
config.baseBackgroundColor = .systemBlue
|
||||
config.baseForegroundColor = .white
|
||||
let cartButton = UIButton(configuration: config)
|
||||
```
|
||||
|
||||
### Button State Handling
|
||||
|
||||
```swift
|
||||
var config = UIButton.Configuration.filled()
|
||||
config.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
|
||||
var outgoing = incoming
|
||||
outgoing.font = .boldSystemFont(ofSize: 16)
|
||||
return outgoing
|
||||
}
|
||||
|
||||
config.configurationUpdateHandler = { button in
|
||||
var config = button.configuration
|
||||
config?.showsActivityIndicator = button.isSelected
|
||||
button.configuration = config
|
||||
}
|
||||
```
|
||||
|
||||
## UIAlertController
|
||||
|
||||
### Alert
|
||||
|
||||
```swift
|
||||
func confirmDeletion() {
|
||||
let alert = UIAlertController(
|
||||
title: "Remove Item?",
|
||||
message: "This cannot be undone.",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "Remove", style: .destructive) { _ in
|
||||
self.performDeletion()
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
present(alert, animated: true)
|
||||
}
|
||||
```
|
||||
|
||||
### Action Sheet
|
||||
|
||||
```swift
|
||||
func showOptions() {
|
||||
let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
sheet.addAction(UIAlertAction(title: "Share", style: .default) { _ in })
|
||||
sheet.addAction(UIAlertAction(title: "Edit", style: .default) { _ in })
|
||||
sheet.addAction(UIAlertAction(title: "Delete", style: .destructive) { _ in })
|
||||
sheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
|
||||
if let popover = sheet.popoverPresentationController {
|
||||
popover.sourceView = optionsButton
|
||||
popover.sourceRect = optionsButton.bounds
|
||||
}
|
||||
|
||||
present(sheet, animated: true)
|
||||
}
|
||||
```
|
||||
|
||||
### Alert with Text Field
|
||||
|
||||
```swift
|
||||
func showInputAlert() {
|
||||
let alert = UIAlertController(
|
||||
title: "Rename",
|
||||
message: "Enter a new name",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
alert.addTextField { textField in
|
||||
textField.placeholder = "Name"
|
||||
textField.autocapitalizationType = .words
|
||||
}
|
||||
|
||||
alert.addAction(UIAlertAction(title: "Save", style: .default) { _ in
|
||||
if let name = alert.textFields?.first?.text {
|
||||
self.rename(to: name)
|
||||
}
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
|
||||
present(alert, animated: true)
|
||||
}
|
||||
```
|
||||
|
||||
## UISearchController
|
||||
|
||||
```swift
|
||||
class SearchableListVC: UIViewController, UISearchResultsUpdating {
|
||||
private let searchController = UISearchController(searchResultsController: nil)
|
||||
private var allItems: [Item] = []
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupSearch()
|
||||
}
|
||||
|
||||
private func setupSearch() {
|
||||
searchController.searchResultsUpdater = self
|
||||
searchController.obscuresBackgroundDuringPresentation = false
|
||||
searchController.searchBar.placeholder = "Search"
|
||||
navigationItem.searchController = searchController
|
||||
definesPresentationContext = true
|
||||
}
|
||||
|
||||
func updateSearchResults(for searchController: UISearchController) {
|
||||
let query = searchController.searchBar.text ?? ""
|
||||
let filtered = query.isEmpty ? allItems : allItems.filter {
|
||||
$0.title.localizedCaseInsensitiveContains(query)
|
||||
}
|
||||
updateItems(filtered)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Search Bar Configuration
|
||||
|
||||
```swift
|
||||
searchController.searchBar.scopeButtonTitles = ["All", "Recent", "Favorites"]
|
||||
searchController.searchBar.showsScopeBar = true
|
||||
searchController.searchBar.delegate = self
|
||||
|
||||
extension SearchableListVC: UISearchBarDelegate {
|
||||
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
|
||||
filterContent(scope: selectedScope)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## UIContextMenuInteraction
|
||||
|
||||
```swift
|
||||
extension PhotoCell: UIContextMenuInteractionDelegate {
|
||||
func contextMenuInteraction(
|
||||
_ interaction: UIContextMenuInteraction,
|
||||
configurationForMenuAtLocation location: CGPoint
|
||||
) -> UIContextMenuConfiguration? {
|
||||
UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
|
||||
let share = UIAction(
|
||||
title: "Share",
|
||||
image: UIImage(systemName: "square.and.arrow.up")
|
||||
) { _ in }
|
||||
|
||||
let favorite = UIAction(
|
||||
title: "Favorite",
|
||||
image: UIImage(systemName: "heart")
|
||||
) { _ in }
|
||||
|
||||
let delete = UIAction(
|
||||
title: "Delete",
|
||||
image: UIImage(systemName: "trash"),
|
||||
attributes: .destructive
|
||||
) { _ in }
|
||||
|
||||
return UIMenu(children: [share, favorite, delete])
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Context Menu with Preview
|
||||
|
||||
```swift
|
||||
func contextMenuInteraction(
|
||||
_ interaction: UIContextMenuInteraction,
|
||||
configurationForMenuAtLocation location: CGPoint
|
||||
) -> UIContextMenuConfiguration? {
|
||||
UIContextMenuConfiguration(
|
||||
identifier: itemID as NSCopying,
|
||||
previewProvider: { [weak self] in
|
||||
return self?.makePreviewController()
|
||||
},
|
||||
actionProvider: { _ in
|
||||
return self.makeMenu()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
func contextMenuInteraction(
|
||||
_ interaction: UIContextMenuInteraction,
|
||||
willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration,
|
||||
animator: UIContextMenuInteractionCommitAnimating
|
||||
) {
|
||||
animator.addCompletion {
|
||||
self.showDetail()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CollectionView Context Menu
|
||||
|
||||
```swift
|
||||
func collectionView(
|
||||
_ collectionView: UICollectionView,
|
||||
contextMenuConfigurationForItemAt indexPath: IndexPath,
|
||||
point: CGPoint
|
||||
) -> UIContextMenuConfiguration? {
|
||||
let item = dataSource.itemIdentifier(for: indexPath)
|
||||
return UIContextMenuConfiguration(identifier: indexPath as NSCopying, previewProvider: nil) { _ in
|
||||
return self.makeMenu(for: item)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*UIKit and Apple are trademarks of Apple Inc.*
|
||||
Reference in New Issue
Block a user