안녕하세요 후르륵짭짭입니다!
이번에는 PageViewController를 이용해서 저렇게 화면 전환을 하는 것을
만들어 보도록 하겠습니다.
** 스토리 보드에 설정하기 **
일단 이렇게 pageViewController를 만들고 그 밑에 ViewController를 만들어주세요
그리고 반드시 pageViewController와 ViewController들의 StoryBoard ID를 설정해주세요!!!
이렇게 새로운 Viewcontroller를 만들어주고 containerView와 stackview를 만들어주세요
그리고 containerView에 드래그 해서 pageView를 가르키게 하고 segue를 embed로 해주셔야합니다!!!
반드시 이름도 설정해주셔야합니다!!!
** Viewcontroller Class 생성하기 **
이제 이렇게 클래스를 만들어 주고 Green , Blue , Red ViewController를 만들어주세요!
** PageViewController 설정해주기 **
-- 전체코드 --
class PageViewController: UIPageViewController , UIPageViewControllerDataSource , UIPageViewControllerDelegate{
var completeHandler : ((Int)->())?
let viewsList : [UIViewController] = {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc1 = storyBoard.instantiateViewController(withIdentifier: "GreenViewController")
let vc2 = storyBoard.instantiateViewController(withIdentifier: "RedViewController")
let vc3 = storyBoard.instantiateViewController(withIdentifier: "BlueViewController")
return [vc1, vc2, vc3]
}()
var currentIndex : Int {
guard let vc = viewControllers?.first else { return 0 }
return viewsList.firstIndex(of: vc) ?? 0
}
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
if let firstvc = viewsList.first {
self.setViewControllers([firstvc], direction: .forward, animated: true, completion: nil)
}
}
func setViewcontrollersFromIndex(index : Int){
if index < 0 && index >= viewsList.count {return }
self.setViewControllers([viewsList[index]], direction: .forward, animated: true, completion: nil)
completeHandler?(currentIndex)
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
completeHandler?(currentIndex)
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = viewsList.firstIndex(of: viewController) else {return nil}
let previousIndex = index - 1
if previousIndex < 0 { return nil}
return viewsList[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = viewsList.firstIndex(of: viewController) else {return nil}
let nextIndex = index + 1
if nextIndex == viewsList.count { return nil}
return viewsList[nextIndex]
}
}
이제 이코드를 하나 씩 분석하도록 하겠습니다.
PageViewController를 사용하기 위해서는 UIPageViewControllerDataSource , UIPageViewControllerDelegate 를 상속 받아야합니다.
우리는 PageViewController에 viewController를 넣을 것이기 때문에
let viewsList : [UIViewController] = {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc1 = storyBoard.instantiateViewController(withIdentifier: "GreenViewController")
let vc2 = storyBoard.instantiateViewController(withIdentifier: "RedViewController")
let vc3 = storyBoard.instantiateViewController(withIdentifier: "BlueViewController")
return [vc1, vc2, vc3]
}()
이렇게 UIViewController 배열을 가진 viewsList 함수를 설정해줍니다!
여기서 중요한 것은 어떤 스토리 보드에 이름이 어떤 view인지 적어줘야합니다!
이제 viewDidLoad() 에 몇 가지 코드를 적어주도록 하겠습니다.
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
if let firstvc = viewsList.first {
self.setViewControllers([firstvc], direction: .forward, animated: true, completion: nil)
}
}
이렇게 우리가 상속 받은 dataSource와 delegate를 self로 맺어주고
viewList.first(첫 번째 배열이 존재하는가 ?) 라면
pageViewController에 있는 self.setViewControllers([firstvc], direction: .forward, animated: true, completion: nil) 를 넣어줍니다.
setViewControllers([보여질 뷰 위치 ], 방향 : 앞/뒤, animated: true, completion: nil) 이렇게 해주시면 됩니다!
이 때 setViewControllers가 배열인 이유는 한페이지에 여러개를 보여줄 수 있기 때문입니다!
이제 기본적으로 설정해줘야할 datasource 함수를 보도록 하겠습니다.
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = viewsList.firstIndex(of: viewController) else {return nil}
let previousIndex = index - 1
if previousIndex < 0 { return nil}
return viewsList[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = viewsList.firstIndex(of: viewController) else {return nil}
let nextIndex = index + 1
if nextIndex == viewsList.count { return nil}
return viewsList[nextIndex]
}
둘의 차이점은 before 냐 after냐 의 차이만 존재합니다.
이 함수는 뷰가 이전할 때 미리 이전 뷰와 앞의 뷰를 감지 해놓는 겁니다.
따라서 저렇게 해준 겁니다.
guard let index = viewsList.firstIndex(of: viewController) else {return nil}
viewList에서 가장 처음에 나오는 함수 매개변수 viewController의 위치를 반환해줍니다. 만약에 없다면 nil!
그래서 before 면 index가 0 보다 커야하고 after면 viewList.count 보다 크거나 같으면 안돼니깐 저렇게 해주고
viewList[다음 위치]를 반환해줍니다!
자!! 여기까지가 기본적인 pageViewContoller의 설정 입니다!
이제 현재 뷰의 위치를 알려주는 것을 만들 도록 하겠습니다.
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
completeHandler?(currentIndex)
}
}
이것 함수는 페이지 이동이 끝나면 호출 되는 함수 입니다. (즉, 스크롤 해서 마쳤을 때 ? 아니면 안 마쳤을 때)
completed를 보면 타입이 Bool 입니다. 따라서 다 끝났을 때, completeHandler 함수를 호출해도록 해줬습니다.
currentIndex는 stored 프로퍼티로 설정 해줬습니다.
var currentIndex : Int {
guard let vc = viewControllers?.first else { return 0 }
return viewsList.firstIndex(of: vc) ?? 0
}
** MainViewController를 설정해주자 **
class MainViewController: UIViewController {
@IBOutlet weak var firstBtn: UIButton!
@IBOutlet weak var secondBtn: UIButton!
@IBOutlet weak var thirdBtn: UIButton!
var btnLists : [UIButton] = []
var currentIndex : Int = 0 {
didSet{
changeBtnColor()
print(currentIndex)
}
}
override func viewDidLoad() {
super.viewDidLoad()
setBtnList()
// Do any additional setup after loading the view.
}
func setBtnList(){
firstBtn.tintColor = .orange
btnLists.append(firstBtn)
btnLists.append(secondBtn)
btnLists.append(thirdBtn)
}
func changeBtnColor(){
for (index, element) in btnLists.enumerated(){
if index == currentIndex{
element.tintColor = .orange
}
else{
element.tintColor = .systemBlue
}
}
}
var pageViewController : PageViewController!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PageViewController" {
print("Connected")
guard let vc = segue.destination as? PageViewController else {return}
pageViewController = vc
pageViewController.completeHandler = { (result) in
self.currentIndex = result
}
}
}
@IBAction func firstBtn(_ sender: Any) {
pageViewController.setViewcontrollersFromIndex(index: 0)
}
@IBAction func secondeBtn(_ sender: Any) {
pageViewController.setViewcontrollersFromIndex(index: 1)
}
@IBAction func thirdBtn(_ sender: Any) {
pageViewController.setViewcontrollersFromIndex(index: 2)
}
}
자,,,,, 여기서 부터 제가 하루종일 고생 했습니다.
PageViewController에서 페이지가 바뀌면 이 정보를 MainViewController에 전달 하고 싶은데,,, 어떻게 전달 해야할 지 몰랐습니다.
물론 Notification을 이용하거나 새롭게 instantiateViewController를 해주면 되지만,,, 너무 지저분 해 보였습니다.
그래서 어떻게 하지,,,, 고민을 많이 했는데,,,
PageViewController는 ContainerView 하부에 존재합니다.
그리고 우리는 이것을 segue로 전달 했습니다. 따라서 segue로 내용을 보내야합니다 ㅠㅠ (stackoverflow.com/questions/47411431/how-can-i-access-data-from-a-container-view-from-the-parent-view-controller-in-s)
var currentIndex : Int = 0 {
didSet{
changeBtnColor()
print(currentIndex)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PageViewController" {
print("Connected")
guard let vc = segue.destination as? PageViewController else {return}
pageViewController = vc
pageViewController.completeHandler = { (result) in
self.currentIndex = result
}
}
}
prepare는 우리가 데이터를 전달 할 때 사용합니다.
prepare는 viewDidLoad()에서 미리 한번 호출 됩니다.
그래서 데이터 전송이 이뤄지기 전에 미리 대기를 하고 있다가 매번 데이터 전송이 이뤄지면 호출 되는 함수 입니다.
이렇게 한번 호출 될 때 completeHandler 클로저의 결과 값을 받아 오도록 했습니다.
(여기에 자세한 글이 있습니다. learnappmaking.com/pass-data-between-view-controllers-swift-how-to/ )
그래서 PageViewContoller에 아래와 같이 Closure 변수를 만들어 준 겁니다.
var completeHandler : ((Int)->())?
(참고로 ? 인 이유는 일단 전역으로 변수를 설정하는 것이기 때문에 초기값을 prepare에서 설정해주게 되므로 nil 값이 존재 할 수 있도록 해줘야합니다. 그래서 completeHandler?(currentIndex) 에 ? 이 있는 이유도 completeHandler의 값이 존재하면 함수를 호출하고 없으면 호출 하지 않도록 한겁니다.)
이제 버튼을 누르면 페이지 변경을 시켜야하는데, 여기서 고생을 또 많이 했습니다.
MainViewController에서 Container에 존재하는 PageViewContoller을 다루기 위해서는 segue 형식으로 접속해야합니다....
이것을 알지 못해 별의 별 방법을 많은 시행착오를 격었습니다. (예를들어 instantiateViewController 사용했습니다. ㅠㅠ)
var pageViewController : PageViewController!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PageViewController" {
print("Connected")
guard let vc = segue.destination as? PageViewController else {return}
pageViewController = vc
pageViewController.completeHandler = { (result) in
self.currentIndex = result
}
}
}
@IBAction func firstBtn(_ sender: Any) {
pageViewController.setViewcontrollersFromIndex(index: 0)
}
@IBAction func secondeBtn(_ sender: Any) {
pageViewController.setViewcontrollersFromIndex(index: 1)
}
@IBAction func thirdBtn(_ sender: Any) {
pageViewController.setViewcontrollersFromIndex(index: 2)
}
위에서 코드를 보면 변수 pageViewController에 vc 값을 넘겨주게 됩니다.
이렇게 하면 segue로 넘어온 PageViewController에 해당하는 주소값이 pageViewController 변수들어 오게 되어 MainViewController에서 PageViewController에 접근할 수 있게 됩니다.
그리고 이제 PageViewController에서 setViewControllerFromIndex 함수를 만들어주시고 위에서 설명한
setViewController 함수를 이용하면 버튼으로 화면 전환이 이뤄어 집니다.
func setViewcontrollersFromIndex(index : Int){
if index < 0 && index >= viewsList.count {return }
self.setViewControllers([viewsList[index]], direction: .forward, animated: true, completion: nil)
completeHandler?(currentIndex)
}
** 버튼 색 변경하기 **
@IBOutlet weak var firstBtn: UIButton!
@IBOutlet weak var secondBtn: UIButton!
@IBOutlet weak var thirdBtn: UIButton!
var btnLists : [UIButton] = []
override func viewDidLoad() {
super.viewDidLoad()
setBtnList()
// Do any additional setup after loading the view.
}
func setBtnList(){
firstBtn.tintColor = .orange
btnLists.append(firstBtn)
btnLists.append(secondBtn)
btnLists.append(thirdBtn)
}
위와 같이 코드를 작성해줍니다. ( setBtnList()는 초기 설정 입니다. )
그리고 위에서 currentIndex 값 변경을 설명했습니다.
이렇게 currentIndex의 값이 변경 된 시점에 chageBtnColor 함수를 호출하게 되고
아래와 같이 변경 해줬습니다.
var currentIndex : Int = 0 {
didSet{
changeBtnColor()
print(currentIndex)
}
}
func changeBtnColor(){
for (index, element) in btnLists.enumerated(){
if index == currentIndex{
element.tintColor = .orange
}
else{
element.tintColor = .systemBlue
}
}
}
부족한 점이 많이 있지만,,,,
긴 글 읽어주셔서 감사합니다!
모두 즐코코하세요!!
참고 :
www.youtube.com/watch?v=u8PFHMEkSAw
stackoverflow.com/questions/47572204/move-pageviewcontroller-with-button
'Xcode > IOS' 카테고리의 다른 글
IOS) 복사 & 붙여넣기를 구현해보자!!! (0) | 2020.09.16 |
---|---|
IOS) UIGesture을 알아보자! (0) | 2020.08.25 |
IOS) UIImagePickerController에 대해서 알아보자 (0) | 2020.08.13 |
IOS) 다국어 Localized에 대해서 알아보자 (1) | 2020.08.12 |
IOS) UserDefault로 간단한 내용을 저장하자! (0) | 2020.08.11 |
댓글