298 lines
8.5 KiB
Markdown
298 lines
8.5 KiB
Markdown
# 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.*
|