처음 시작할 때에만 나타나는 Tutorial 화면 구성하기(PageViewController 이용)

이번에는 앱을 처음 시작할 때만 실행하고 이후부터는 나타나지 않는 Tutorial 화면을 구성해 보자.

아래와 같이 구동된다.




==========================================================================
1. 메인 ViewController와 Tutorial View Controller와의 연결 (첫 실행인지 아닌지 확인 필요)
2. Tutorial View Controller에 pageViewController를 자식view로 설정
==========================================================================

1.  MainViewController와 Tutorial View Controller와의 연결 (첫 실행인지 아닌지 확인 필요)
. 앱은 MainViewController로 시작하지만 만약 앱을 처음 시작한다면 TutorialViewController이 화면에 뜨게 만든다. 그리고 TutorialViewController에서 버튼을 click하면 MainViewController로 다시 돌아오게 하고 이후부터는 TutorialViewController가 나타나지 않게 한다.
. 우선 Storyboard를 하나 추가하고 Tutorial.storyboard로 이름을 변경한다.(File>New>File... 에서 storyboard 선택)
   -> 이런 Tutorial 처럼 첫 실행에만 실행된다면 Main.storyboard와는 다른 storyboard에서 관리하는게 보기에 좋다.
. Tutorial.storyboard에 ViewController를 추가하고 Storyboard ID를 Master라고 명명한다.
  -> Storyboard ID를 설정해야 향후 코드로 viewController를 읽을 수 있다.
. Swift file을 UIViewController 형식으로 추가를 하고 이름을 MasterTutorial로 명명한다. 그리고 storyboard에 추가된 viewController의 class로 설정해 준다.
. MasterTutorial ViewController 하단에 UIButton을 하나 추가하고 Start로 명명하였다.
아래 그림처럼 되었다.

. 이제 첫 실행 여부에 따라 MasterTutorial이 화면에 나오게 하고 MasterTutorial에서 start를 누르면 MainViewController로 돌아가는 코드를 만들어 보자
. 우선 MainViewController 화면이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MainViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    override func viewDidAppear(_ animated: Bool) {
// (1) 첫 실행인지 확인
        let ud = UserDefaults.standard
        if ud.bool(forKey: "tutorials"== false {
// (2) MasterTutorial viewController 실행
            let sb = UIStoryboard(name: "Tutorial", bundle: Bundle.main)
            let vc = sb.instantiateViewController(withIdentifier: "Master")
            self.present(vc, animated: true, completion: nil)
        }
        
    }
}
cs

(1) 첫 실행인지 확인
 . UserDefaults객체에 tutorials key로 저장된 내용을 불러와서 false인지 확인한다. key값으로 tutorials가 없어도 false가 반환되니 처음 실행한다면 false가 반환되어 if구문 안의 내용이 실행된다.
(2) MasterTutorial viewController 실행
 . 어느 storyboard에 있는지 확인하고 그 storyboard에서 ID값이 Master인 viewcontroller를 불러온다.
 . 여기선 Tutorial.storyboard에 존재하는 MasterTutorial viewcontroller를 가져왔다.
 . 만약 동일 storyboard에 존재하는 viewcontroller를 가져온다면 self.storyboard.instantiateViewController()로 적용하면 된다. 여기선 storyboard를 분리했기 때문에 이렇게 storyboard를 따로 가져와야 한다.
# 여기서 viewDidAppear에 실행하였다. viewDidLoad()에서 실행한다면 화면이 연결되지 않는다. MainViewController가 실행되고 이후에 self.present()로 연결해야 하기 때문에 view가 나타난 후 연결하기 위해 viewDidAppear()에 코딩해야 한다.

. 이번엔 MasterTutorial 화면이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
class MasterTutorial: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    }
  // (1) startButton IBAction 연결
    @IBAction func startButton(_ sender: Any) {
// (2) data저장
        let ud = UserDefaults.standard
        ud.set(true, forKey: "tutorials")
        ud.synchronize()
// (3) MainViewController로 돌아가기
        self.dismiss(animated: true, completion: nil)
    }
}
cs

(1) startButton IBAction 연결
 . 우선 storyBoard에서 start 버튼을 IBAction으로 연결한다. (control drag)
(2) data 저장
 . UserDefaults객체에 tutorials key로 Bool값인 true를 저장하고 싱크한다. 이렇게 해야 다음 앱 실행 때 tutorial 화면이 나오지 않게 된다.
(3) MainViewController로 돌아가기
 . MainView에서 present로 들어왔기 때문에 dismiss로 MainView로 돌아갈 수 있다.

아래는 여기까지 구동 화면이다. 참고로 MainView와 MasterTutorial view의 바탕화면 색을 설정하였다.

2. Tutorial View Controller에 pageViewController를 자식view로 설정
. 자 이제 pageViewController를 이용하여 tutorial 화면을 구성해 보자.
. 우선 화면 구성을 아래와 같이 PageViewController와 UIViewController를 추가해 주자.
. PageViewController는 PageView로 UIViewController는 ContentsView로 각각 Storyboard ID 설정한다.
. UIViewController에는 ImageView와 Label을 그림과 같이 추가해 준다.
. swift 화일을 UIViewController class로 하나 추가하고 이름을 ContentsTutorial로 명명한다. 그리고 storyboard에서 contentsView로 설정된 ViewController의 class로 설정해 준다.
. 아래 그림과 같이 되었다.

. 이제 ContentsTutorial class를 작성해 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ContentsTutorial: UIViewController {
// (1)
    @IBOutlet weak var pageLabel: UILabel!
    @IBOutlet weak var pageImage: UIImageView!
    // (2)
    var pageLabelText: String!
    var pageImageName: String!
    var pageIndex: Int!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.pageLabel.text = pageLabelText
        self.pageImage.image = UIImage(named: pageImageName)
    }
}
cs
(1) outlet 연결
 . 화면에 표시항 Label과 Image를 IBOutlet으로 연결하였다.
(2) Contents 정보 정의
 . MasterTutorial에서 받아올 정보를 정의하였다.

. 이번엔 다시 MasterTutorial이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class MasterTutorial: UIViewController, UIPageViewControllerDataSource {
    // (1) contentsPageName 정의
    let contentsPageName = ["page0","page1","page2","page3"]
    override func viewDidLoad() {
        super.viewDidLoad()
// (3) PageViewController를 설정한다.
        let pageVC = self.storyboard?.instantiateViewController(withIdentifier: "PageView"as! UIPageViewController
        pageVC.dataSource = self
        pageVC.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height - 30)
        pageVC.setViewControllers([getContentsView(pageIndex: 0)!], direction: .forward, animated: true, completion: nil)
        self.addChildViewController(pageVC)
        self.view.addSubview(pageVC.view)
        pageVC.didMove(toParentViewController: self)
    }
// (2) contentsView 가져오는 함수 정의
    func getContentsView(pageIndex index:Int)->UIViewController? {
        guard index < self.contentsPageName.count else {return nil}
        let vc = self.storyboard?.instantiateViewController(withIdentifier: "ContentsView"as! ContentsTutorial
        vc.pageImageName = self.contentsPageName[index]
        vc.pageLabelText = self.contentsPageName[index]
        vc.pageIndex = index
        return vc
    }
// (4) UIPageViewControllerDataSource 프로토콜 정의 함수
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard let index = (viewController as? ContentsTutorial)?.pageIndex else {return nil}
        if index > 0 {
            return getContentsView(pageIndex: index - 1)
        }
        return nil
    }
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard let index = (viewController as? ContentsTutorial)?.pageIndex else {return nil}
        if index + 1 < self.contentsPageName.count {
            return getContentsView(pageIndex: index + 1)
        }
        return nil
    }
// (5) pageIndicator 설정
    func presentationCount(for pageViewController: UIPageViewController) -> Int {
        return self.contentsPageName.count
    }
    func presentationIndex(for pageViewController: UIPageViewController) -> Int {
        return 0
    }
    @IBAction func startButton(_ sender: Any) {
        ....
    }
}
cs

(1) contentsPageName 정의
 . ContentsTutorial class에 넘어갈 정보이다. 이전에 아래와 같이 assets에 page0~4까지 그림을 추가하였다. 추가한 그림의 이름과 동일한 string으로 array를 만들었다.

(2) ContentsTutorial View 가져오는 함수 정의
 . index 정수를 받아서 어떤 ContentsTutorial을 반환하는지에 대한 함수이다.
 . index는 contentsPageName array의 개수를 넘으면 안되므로 guard문을 이용했다.
 . 동일한 storyboard이므로 self.storyboard.instantiateViewController()를 이용해서 ContentsTutorial class를 불러왔다.
 . 불러온 ContentsTutorial class에서 표시될 내용(image 이름, pageIndex)를 index와 연결하여 대입하였다.
 . 이렇게 설정된 ContentsTutorial을 반환한다.
(3) PageViewController를 설정한다.
 . (2)처럼 동일 storyboard에 있는 pageViewController를 가져와서 pageVC에 대입해 준다.
 . 먼저 UIPageViewControllerDataSource 프로토콜 따르도록 dataSource를 self로 지정한다. 이렇게 해야 page를 넘길때 어떤 page가 다음에 와야 하는지 이전 page는 머가 있는지 지정할 수 있다.
 . pageVC의 위치 및 크기를 설정해 준다. 다만 start button이 가져지지 않게 height는 self.view에 조금 모자라게 설정한다.
 . setViewControllers() method로 어떤 viewController를 pageVC에 첫번째로 나타나게 할지 설정해 준다.
 . pageVC를 자식뷰로 설정하고 MasterTutorial을 pageVC의 부모뷰로 알려준다. 이렇게 custom container view controller를 설정할 때 항상 나오는 형식이다.
(4) UIPageViewControllerDataSource protocol 정의 함수
 . PageView에서 어떤 view가 나올지 정의를 해줘야 한다. 하여 before view는 먼지, after view는 먼지 알려줘야 PageView에서 준비하고 있을 수 있다. tableView에서 dataSource 프로토콜처럼..
 . 여기 두 함수에서 viewController 인자는 현재 viewController를 뜻하는 것이다. 하여 ContentsTutorial로 type casting하고 현재 view의 index가 얼마인지 확인한다. 그리고 맨처음인지 아니면 맨 마지막 view인지 확인해서 before, after view가 무엇인지 지정해 주면 된다.
(5)마지막으로 pageIndicator 설정하기 위한 함수이다.
 . 총 page가 몇개인지, 그리고 최초 page의 정보는 어떻게 되는지에 대해 알려준다.

. 이렇게 하고 실행하더라도 문제 없지만 indicator의 색을 수정하는게 좋을거 같다. 하여 아래와 같이 AppDelegate에 코드를 수정하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        //(1) appearence 설정
        let appearence = UIPageControl.appearance()
        appearence.pageIndicatorTintColor = UIColor.lightGray
        appearence.currentPageIndicatorTintColor = UIColor.black
        
        return true
    }
......
}
cs

(1) appearence 설정
 . 모든 UIPageControl의 외관에 대한 내용을 수정을 한다.
 . 이렇게 수정을 하면 UIPageControl의 indicator의 색과 선택된 indicator의 색에 대해 설정할 수 있다.
 . 만약 TableView나 UIViewController또한 이런 방식으로 외관에 대한 내용을 설정할 수 있다. 각각의 UIViewController class에 들어가서 설정하지 않더라도 모두 동일한 concept으로 설정 가능하다.

댓글

이 블로그의 인기 게시물

N-Calculator 개인정보 처리

알림창 커스터마이징 (Customizing AlertController) - I

Sudoku_GGagi 개인정보 처리방침