ScrollView는 iOS에서 콘텐츠가 화면보다 클 때 스크롤을 통해 모든 콘텐츠를 볼 수 있게 해주는 뷰이다. 주로 다음과 같은 상황에서 사용된다:
class ScrollViewController: UIViewController {
// ScrollView 선언
private let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
// 스크롤 인디케이터 설정
scrollView.showsVerticalScrollIndicator = true
scrollView.showsHorizontalScrollIndicator = false
return scrollView
}()
// ContentView 선언 - 실제 내용을 담을 컨테이너 뷰
private let contentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
setupScrollView()
}
private func setupScrollView() {
// 뷰 계층 구조 설정
view.addSubview(scrollView)
scrollView.addSubview(contentView)
// 제약조건 설정
NSLayoutConstraint.activate([
// ScrollView 제약조건
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// ContentView 제약조건
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
// 가로 스크롤을 막기 위한 width 설정
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
}
}
private func addContent() {
// StackView를 사용하여 콘텐츠 정렬
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 20
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stackView)
// StackView 제약조건
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
])
// 콘텐츠 추가 예시
for i in 0..<10 {
let label = UILabel()
label.text = "Content \(i)"
label.textAlignment = .center
label.backgroundColor = .systemGray6
label.heightAnchor.constraint(equalToConstant: 100).isActive = true
stackView.addArrangedSubview(label)
}
}
class ScrollViewController: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
// 스크롤 시작
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
print("스크롤 시작")
}
// 스크롤 중
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
print("현재 스크롤 위치: \(offset)")
}
// 스크롤 종료
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
print("스크롤 종료")
}
}
class PagingScrollViewController: UIViewController {
private let scrollView: UIScrollView = {
let scroll = UIScrollView()
scroll.isPagingEnabled = true // 페이징 활성화
scroll.translatesAutoresizingMaskIntoConstraints = false
return scroll
}()
private func setupPaging() {
// 페이지 크기 설정
let pageWidth = view.frame.width
let pageHeight = view.frame.height
// 여러 페이지 추가
for i in 0..<3 {
let pageView = UIView()
pageView.translatesAutoresizingMaskIntoConstraints = false
pageView.backgroundColor = [.red, .green, .blue][i]
scrollView.addSubview(pageView)
NSLayoutConstraint.activate([
pageView.widthAnchor.constraint(equalToConstant: pageWidth),
pageView.heightAnchor.constraint(equalToConstant: pageHeight),
pageView.topAnchor.constraint(equalTo: scrollView.topAnchor),
pageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor,
constant: pageWidth * CGFloat(i))
])
}
// 전체 콘텐츠 크기 설정
scrollView.contentSize = CGSize(width: pageWidth * 3, height: pageHeight)
}
}
class ZoomableScrollViewController: UIViewController, UIScrollViewDelegate {
private let scrollView: UIScrollView = {
let scroll = UIScrollView()
scroll.minimumZoomScale = 1.0
scroll.maximumZoomScale = 3.0
return scroll
}()
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
return imageView
}()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
// UIScrollViewDelegate 메서드
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
// 줌 시 이미지 중앙 정렬
let offsetX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
let offsetY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
scrollView.contentInset = UIEdgeInsets(top: offsetY, left: offsetX,
bottom: 0, right: 0)
}
}
class ScrollViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupKeyboardHandling()
}
private func setupKeyboardHandling() {
// 키보드 노티피케이션 등록
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
}
@objc private func keyboardWillShow(notification: Notification) {
guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
return
}
let contentInsets = UIEdgeInsets(
top: 0,
left: 0,
bottom: keyboardFrame.height,
right: 0
)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
}
@objc private func keyboardWillHide(notification: Notification) {
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = .zero
}
}
extension ScrollViewController {
// 특정 위치로 스크롤
func scrollToPosition(y: CGFloat) {
let point = CGPoint(x: 0, y: y)
scrollView.setContentOffset(point, animated: true)
}
// 특정 뷰로 스크롤
func scrollToView(_ view: UIView) {
let rect = scrollView.convert(view.bounds, from: view)
scrollView.scrollRectToVisible(rect, animated: true)
}
}
// 스크롤 중 이미지 로딩 최적화
extension ScrollViewController {
private func optimizeScrollPerformance() {
// 스크롤 중 이미지 로딩 일시 중지
scrollView.decelerationRate = .fast
// 메모리 사용량 최적화
scrollView.contentInsetAdjustmentBehavior = .never
// 스크롤 중 그림자 렌더링 비활성화
view.layer.shouldRasterize = true
view.layer.rasterizationScale = UIScreen.main.scale
}
}
TableView는 iOS에서 가장 많이 사용되는 UI 컴포넌트 중 하나로, 데이터를 리스트 형태로 표시하는 데 사용된다. 다음과 같은 특징을 가진다:
class TableViewController: UIViewController {
// TableView 선언
private let tableView: UITableView = {
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
// 기본 셀 등록
table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
return table
}()
// 데이터 소스
private var items: [String] = ["Item 1", "Item 2", "Item 3"]
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
private func setupTableView() {
// 뷰에 TableView 추가
view.addSubview(tableView)
// 델리게이트 설정
tableView.delegate = self
tableView.dataSource = self
// 제약조건 설정
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}
// TableView 프로토콜 구현
extension TableViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Selected: \(items[indexPath.row])")
tableView.deselectRow(at: indexPath, animated: true)
}
}
class CustomTableViewCell: UITableViewCell {
static let identifier = "CustomTableViewCell"
// UI 컴포넌트 정의
private let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .systemFont(ofSize: 16, weight: .bold)
return label
}()
private let descriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .systemFont(ofSize: 14)
label.textColor = .gray
return label
}()
// 초기화
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupCell()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// 셀 설정
private func setupCell() {
contentView.addSubview(titleLabel)
contentView.addSubview(descriptionLabel)
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
descriptionLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
descriptionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
descriptionLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12)
])
}
// 데이터 설정 메서드
func configure(with item: Item) {
titleLabel.text = item.title
descriptionLabel.text = item.description
}
}
class SectionedTableViewController: UIViewController, UITableViewDataSource {
// 섹션별 데이터 구조
struct Section {
let title: String
var items: [String]
}
private var sections: [Section] = [
Section(title: "Section 1", items: ["Item 1", "Item 2"]),
Section(title: "Section 2", items: ["Item 3", "Item 4"])
]
// 섹션 개수
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
// 섹션별 행 개수
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].items.count
}
// 섹션 헤더 타이틀
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].title
}
// 셀 구성
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]
return cell
}
}
extension TableViewController {
// 스와이프 액션 설정
func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
// 삭제 액션
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { [weak self] (_, _, completion) in
self?.deleteItem(at: indexPath)
completion(true)
}
deleteAction.backgroundColor = .red
// 편집 액션
let editAction = UIContextualAction(style: .normal, title: "Edit") { [weak self] (_, _, completion) in
self?.editItem(at: indexPath)
completion(true)
}
editAction.backgroundColor = .blue
return UISwipeActionsConfiguration(actions: [deleteAction, editAction])
}
private func deleteItem(at indexPath: IndexPath) {
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
private func editItem(at indexPath: IndexPath) {
// 편집 로직 구현
}
}
class SearchTableViewController: UIViewController, UISearchResultsUpdating {
private let searchController = UISearchController(searchResultsController: nil)
private var filteredItems: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
setupSearchController()
}
private func setupSearchController() {
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search Items"
navigationItem.searchController = searchController
definesPresentationContext = true
}
func updateSearchResults(for searchController: UISearchController) {
guard let searchText = searchController.searchBar.text?.lowercased() else { return }
filteredItems = items.filter { item in
return item.lowercased().contains(searchText)
}
tableView.reloadData()
}
// TableView DataSource 수정
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchController.isActive ? filteredItems.count : items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let item = searchController.isActive ? filteredItems[indexPath.row] : items[indexPath.row]
cell.textLabel?.text = item
return cell
}
}
class OptimizedTableViewCell: UITableViewCell {
// 셀 재사용 시 초기화가 필요한 프로퍼티들
override func prepareForReuse() {
super.prepareForReuse()
// 이미지뷰 초기화
imageView?.image = nil
// 레이블 초기화
titleLabel.text = nil
descriptionLabel.text = nil
// 진행 중인 작업 취소
imageLoadingTask?.cancel()
}
}
extension TableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
// 미리 데이터 로드
for indexPath in indexPaths {
// 이미지나 데이터 미리 로드
preloadData(for: indexPath)
}
}
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
// 필요없어진 프리패치 작업 취소
for indexPath in indexPaths {
cancelPreload(for: indexPath)
}
}
}
extension TableViewController {
private func optimizeTableView() {
// 고정 높이 설정
tableView.rowHeight = 60
// 또는 자동 높이 계산
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 60
}
}