오늘은 아니 어제는 메인 화면에 있는 이미지를 스와이프하여 썸네일 대신에 여러 이미지를 보여주었으면 좋겠어서 그 부분을 구현해보았습니다. 제가 원하는 구조를 만들기 위해서는 ScrollView와 UIPageControl을 사용하더라구요!
어제 구현했지만, 오늘 블로그를 적는 이유는 생각보다 UIPageControl에서 복잡한 부분이 있어서 추가적인 공부를 한 후에 돌아왔습니다.
그럼 시작해봅시다!!😄
UIPageControl에대한 자세한 설명은 아래 블로그를 참고해주세요💗
https://jihae-qu.tistory.com/87
https://jihae-qu.tistory.com/88?category=1091986
UIPageControl과 ScrollView를 사용한 이미지 스와이프 구현
UI 설정
기존에 위치했던 ImageView를 삭제하고 그 자리에 새로운 View를 얻고, 그 위에 ScrollView를 올렸습니다.
PageControl은 따로 ImageView와 나란히 올려주었어요!
현재 설정한 AutoLayout 입니다.
여기서 ScrollView의 ContentView이 없기때문에 빨간 오류가 지워지지 않는데요! ContentView는 코드에서 만들어서 프레임을 완벽히 잡아줄 예정이라 빨간 에러는 무시하셔도 됩니다!
ViewController와 연결하기
@IBOutlet weak var imageScrollView: UIScrollView!
@IBOutlet weak var pageControl: UIPageControl!
방금 UI에 올려둔 ScrollView와 pageControl을 ViewController와 연결해줍니다. 저는 원래의 ScrollView가 존재해서 구분하기 위해 imageScrollView라고 설정해주었습니다.
ScrollView에 올라갈 image 배열 설정하기
scrollView에 올라갈 UIImage를 저장할 배열을 선언해주고,
var images = [UIImage]()
해당 images들을 불러와 배열에 저장해줍니다! 근데 이 images들은 현재 URLSession을 통해 불러온 데이터 안에 URL 형태로 [String] 배열에 저장이 되어있습니다. 이 URL 형태의 String 배열의 데이터들을 다시 한번 URLSession을 통해서 UIImage로 변경하는 과정을 거쳐야 합니다. 그러기 위해서는 데이터가 저장되어 있는 setData()에서 이 작업을 해주어야 합니다.
func setData(_ product: Product) {
// 코드 생략
images.removeAll()
imageScrollView.subviews.forEach { $0.removeFromSuperview() } // 기존 이미지 뷰 제거
for imageURL in product.images {
if let url = URL(string: imageURL) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, error == nil {
DispatchQueue.main.async {
if let image = UIImage(data: data) {
self.images.append(image)
// scrollView 설정 함수
// pageControl 설정 함수
}
}
}
}.resume()
}
}
}
product에 저장된 Product 데이터들을 가져와서 images 배열에 넣기 전에 해당 전에 데이터가 들어있을 images 배열과 scrollView에 담긴 UIImageView를 제거해줍니다.
URLSession 통신은 자체적으로 비동기적으로 작업을 수행하기 때문에 images에 값을 저장한다고 해도 다른 곳에서 images를 사용할 때는 작업이 끝나지 않은 상황이라 images가 비어있을 수도 있습니다. 이를 방지하기 위해서 scrollView와 pageControll이 설정되는 함수는 DispatchQueue.main.async 함수 내에서 실행이 되어야 합니다!
ScrollView 설정하기
func addContentScrollView() {
var xOffset: CGFloat = 0
for image in images {
let imageView = UIImageView(image: image)
imageView.frame = CGRect(x: xOffset, y: 0, width: imageScrollView.bounds.width, height: imageScrollView.bounds.height)
imageView.contentMode = .scaleAspectFit
imageScrollView.addSubview(imageView)
xOffset += imageScrollView.bounds.width
}
imageScrollView.contentSize = CGSize(width: xOffset, height: imageScrollView.bounds.height)
}
ScrollView내에 ContentView가 될 UIImageView를 설정해주고, 해당 ImageView의 프레임을 설정해줍니다. 각 ImageView를 ScrollView의 ContentView로 올려줍니다.
이 때, x
와 y
는 프레임의 위치를 설정해주고, width
와 height
는 프레임의 크기를 설정해줍니다. x의 위치는 요소가 하나씩 늘어갈때마다 해당 imageView의 너비를 더해주면서 각 imageView의 요소 개수에 따른 x값을 정해주고, y의 위치는 Top으로 정렬될 예정이니 0으로 고정해줍니다.
여기서 .scaleAspectFit
은 이미지의 비율을 고정한 채로 맞추도록 설정하는 것입니다.
imageScrollView의 contentSize
는 보여지는 화면의 크기가 아닌 실제의 content가 올라갈 content의 실제 사이즈로 높이를 고정하고, 너비를 imageView의 개수에 맞추도록 설정합니다.
PageControll 설정하기
func setPageControl() {
pageControl.numberOfPages = images.count
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let value = scrollView.contentOffset.x/scrollView.frame.size.width
pageControl.currentPage = Int(round(value))
}
pageControl의 초기값 설정인 numberOfPages
를 설정하여 pageControl의 페이지 갯수를 설정합니다.
또한 현재 수평 스크롤의 위치로 사용자에게 보여지고 있는 페이지 번호를 계산하여 pageControl의 현재 페이지 번호를 입력합니다. scrollViewDidScroll
함수를 사용하기 위해서는 UIScrollViewDelegate
를 채택하여 imageScrollView.delegate = self
함수를 꼭 적어주어야 합니다!!
전체코드
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var productBrandLabel: UILabel!
@IBOutlet weak var productTitleLabel: UILabel!
@IBOutlet weak var productPriceLabel: UILabel!
@IBOutlet weak var productDescriptionLabel: UILabel!
@IBOutlet weak var addToWishButton: UIButton!
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var imageScrollView: UIScrollView!
@IBOutlet weak var pageControl: UIPageControl!
let dataManager = ProductDataManager()
var coreDataManager = ProductCoreDataManager.shared
var randomid = 0
var currentProduct: Product?
var images = [UIImage]()
// MARK: - ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
setRandomID()
setDataAndLabel()
setRefreshControl()
imageScrollView.delegate = self
addContentScrollView()
setPageControl()
}
// MARK: - setDataAndLabel
func setRandomID() {
guard let randomId = (1...100).randomElement() else { return }
randomid = randomId
}
func setPriceLabel(price: Int) -> String {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
guard let priceString = numberFormatter.string(from: NSNumber(value: price)) else { return "" }
return "$ " + priceString
}
func setDataAndLabel() {
dataManager.getProductData(id: randomid ) { product in
DispatchQueue.main.async {
if let product = product {
dump(product)
self.setData(product)
}
}
}
}
func setData(_ product: Product) {
currentProduct = product
productBrandLabel.text = product.brand
productTitleLabel.text = product.title
productPriceLabel.text = setPriceLabel(price: product.price)
productDescriptionLabel.text = product.description
images.removeAll()
imageScrollView.subviews.forEach { $0.removeFromSuperview() } // 기존 이미지 뷰 제거
for imageURL in product.images {
if let url = URL(string: imageURL) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, error == nil {
DispatchQueue.main.async {
if let image = UIImage(data: data) {
self.images.append(image)
self.addContentScrollView()
self.setPageControl()
}
}
}
}.resume()
}
}
}
// MARK: - PageControl & scrollView
func addContentScrollView() {
var xOffset: CGFloat = 0
for image in images {
let imageView = UIImageView(image: image)
imageView.frame = CGRect(x: xOffset, y: 0, width: imageScrollView.bounds.width, height: imageScrollView.bounds.height)
imageView.contentMode = .scaleAspectFit
imageScrollView.addSubview(imageView)
xOffset += imageScrollView.bounds.width
}
imageScrollView.contentSize = CGSize(width: xOffset, height: imageScrollView.bounds.height)
}
func setPageControl() {
pageControl.numberOfPages = images.count
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let value = scrollView.contentOffset.x/scrollView.frame.size.width
pageControl.currentPage = Int(round(value))
}
// MARK: - refreshControl
func setRefreshControl() {
scrollView.refreshControl = UIRefreshControl()
scrollView.refreshControl?.attributedTitle = NSAttributedString(string: "Pull to refresh")
scrollView.refreshControl?.addTarget(self, action: #selector(self.refresh(_:)), for: .valueChanged)
}
@objc func refresh(_ sender: AnyObject) {
DispatchQueue.main.asyncAfter(deadline: .now()+1){
self.setRandomID()
self.setDataAndLabel()
self.scrollView.refreshControl?.endRefreshing()
}
}
// MARK: - addToWishButton
@IBAction func addToWishTapped(_ sender: UIButton) {
guard let product = currentProduct else { return }
coreDataManager.setProductCoreData(data: product)
}
// MARK: - nextToItem
@IBAction func nextToItemTapped(_ sender: UIButton) {
setRandomID()
setDataAndLabel()
}
}
구현 화면
끝!!! 다음은 앱 아이콘을 변경해봅시다!