본문 바로가기
개발(Development)/iOS(아이폰)

[한글 번역_09] Start Developing iOS Apps (Swift) - Working with Table Views > Implement Navigation

by 카레유 2015. 10. 30.
안녕하세요 카레유입니다.

Apple에서 제공해주는 Swift iOS 앱 개발 가이드를 한글 번역해보았습니다.

영어실력도, 개발실력도, 심지어 한국어 실력도 미흡합니다.

부족한 부분이 많지만, 많은 도움 되시길 바라겠습니다.

아직 작성 중인 내용으로 시간을 갖고 개선해 나갈 생각입니다.

감사합니다.



Implement Navigation

이번 레슨에서는 navigation controller와 segue를 이용하여 네비게이션 및 화면 전환 기능을 구현하겠습니다. 이번 레슨을 통해 완성될 앱의 모습은 아래와 같습니다.

image

Learning Objectives

At the end of the lesson, you’ll be able to:

  • 스토리보다 상에서 ViewController를 navigation controller에 삽입(embed)합니다

  • view controller간에 segue를 생성합니다.

  • 스토리보드에서 Attributes inspector를 이용하여 segue의 속성을 편집합니다.

  • prepareForSegue(_:sender:)메서드를 통해 view controller 간에 데이터를 전달합니다

  • unwind segue를 활용합니다.

  • stack view를 사용하여 견고하고, 유연한 레이아웃을 만듭니다.

Add a Segue to Navigate Forward

작업한 대로 데이터가 노출되고 있기 때문에, 이제는 음식 목록 화면과 상세화면 사이의 이동이 가능한 네비게이션 기능을 구현해야합니다. 화면(scene)간의 전환을 segue라고 합니다.

이 작업을 위해서는 각 화면(scene)들을 navigation controller 안에 삽입시키고, 삽입된 화면 간에 segue를 구현하면 됩니다. navigation controller안에 삽입된 view controller들을 navigation stack이라고 하며, 이 스택에 가장 먼저 삽입되어 맨 아래에 위치하고 있는 root view controller는 스택에서 제거(pop)되지 않습니다. 스택에 저장된 view controller들 중에서 필요한 화면을 꺼내면서 화면전환 작업을 구현한다고 할 수 있습니다.

To add a navigation controller to your meal list scene

  1. Main.stroryboard 스토리보드 를 열어주세요

  2. scene dock에서 table view controller를 선택해주세요

    image
  3. table view controller가 선택된 상태에서 "Editor > Embed In > Navigation Controller"을 클릭하세요. 

    스토리보드에 navigation controller가 생성됩니다. 화살표 형태로 표시되어 있는 스토리보드 시작점(storyboard entry point)을 이 navigation controller로 드래그하여 옮겨주세요. navigation controller와 table view controller 사이에 관계(Relationship)가 생성됩니다.

image

캔버스에 navigation controller와 table view controller를 연결하는 "root view controller" 관계 아이콘이 생성되었습니다. 이 것은 table view controller가 navigation controller 의 root view controller 라는 의미입니다. 스토리보드 시작점(storyboard entry point)는 현재 navigation controller에 설정되어 있습니다. navigation controller가 일종의 컨테이너가 되어, table view controller를 담고 있는 형태이기 때문에 navigation controller가 시작점이 되어야 합니다.

현재 테이블 뷰의 상단에는 네비게이션 바(navigation bar)가 생성되어 있습니다. 네비게이션 스택(navigation stack)에 포함되어 있는 모든 view controller는 네이게이션 바를 갖게되며, 네비게이션 바에는 앞/뒤 화면으로 이동할 수 있는 컨트롤(control, 버튼 등)을 갖을 수 있습니다. 음식 목록 화면에서 음식 상세 화면으로 이동하기 위한 버튼을 네비게이션 바에 추가하는 작업도 진행할 예정입니다.

Checkpoint: 앱을 실행시켜 보세요. 테이블 뷰의 상단에 네비게이션 컨트롤러에 의해 생성된 네비게이션 바가 표시됩니다. 네비게이션바는 상단바(status bar)바로 아래까지만 노출되므로 더이상 컨텐츠가 겹쳐보이지 않습니다.

image

Configure the Navigation Bar for the Scenes

네비게이션 바에 타이틀을 추가하고, 새로운 음식을 추가할 수 있는 화면으로 이동하는 버튼을 만들어 보겠습니다. 네비게이션 바의 타이틀은 네비게이션 컨트롤러가 현재 노출하고 있는 뷰컨트롤러 부터 받아옵니다. 즉, 네비게이션 바에 바로 타이틀을 작성하지 않고, 이 화면을 관리하는 테이블 뷰 컨트롤러에 있는 네비게이션 아이템(navigation item)을 이용하여 타이틀을 설정하겠습니다.

To configure the navigation bar in the meal list

  1. 음식목록화면을 보여주는 table view controller의 scene에서 네비게이션 바의 가운데 부분을 더블클릭 하세요

    image

     커서가 나타나면서 텍스트를 입력할 수 있게 됩니다.

  2. "Your Meals"라고 입력하고 리턴키를 눌러 저장하세요

    image
  3. Object library를 열어 주세요(메뉴바에서 View > Utilities > Show Object Library 를 클릭해도 됩니다)

  4. Object library에서 Bar Button Item을 찾으세요

  5. Bar Button Item 객체를 드래그해서 네비게이션 바 오른쪽 끝에 추가하세요

    아래와 같이 Item이라고 적힌 button이 나타납니다.

    image
  6. bar button item을 선택한 상태에서 Attributes inspector를 열어주세요

  7. Attributes inspector의 System Item option 옆의 팝업메뉴에서 Add를 선택해주세요(Xcode6에서는 Identifier) 

    버튼이 ( + ) 형태의 Add버튼으로 변경됩니다.

    image

Checkpoint: 앱을 실행시켜주세요. 네비게이션 바에 타이틀과 Add(+)버튼이 생성되었습니다. 하지만 Add(+)버튼은 아직 아무런 동작도 하지 않습니다. 지금부터 만들어 보겠습니다.

image

Add(+) 버튼을 누르면 음식 상세화면으로 화면전환(segue)이 이루어져야 합니다.

To configure the Add button in the meal scene

  1. 캔버스에서 Add(+)버튼을 선택하세요

  2. Control을 누른채 드래그하여 버튼을 음식상세화면(meal scene)으로 연결하세요

    image

    드래그를 마치는 순간 "Action segue"라는 단축메뉴가 나타납니다.

    image

    Action Segue메뉴에서는 Add(+)버튼을 눌렀을 때, 음식목록화면에서 음식상세화면으로 전환하기 위해 어떤 타입의 화면전환(segue)를 사용할 것인지를 설정할 수 있습니다.

  3. show를 선택해주세요

segue의 방식은 "show"로 설정했으며, 네비게이션 컨트롤러에 나타날 화면은 음식상세화면으로 지정하였습니다. 이제 음식상세화면에서도 네비게이션 바가 나타납니다.

image

Checkpoint: 다시 앱을 실행시켜보세요. Add(+)버튼을 누르면 음식목록화면(meal list scene)에서 음식상세화면(meal scene)으로 이동합니다. "show" 타입의 segue를 설정하여 네비게이션 컨트롤러를 이용하면, 백버튼을 통해 다시 이전화면으로 돌아갈 수 있는 기능도 제공됩니다. 자동으로 생성된 백버튼을 클릭하면 음식목록화면으로 돌아옵니다.

image

"show"타입의 segue를 사용하면 push-style(오른쪽 화면이 밀려들어옴)의 navigation이 제공되는데, 아이템을 추가하는데 적절하진 않습니다. push navigation은 drill-down 인터페이스에 최적화 되어있으며, 이는 사용자가 선택한 것에 대한 상세정보를 제공하기에 적합니다. 아이템을 추가하는 것은 사용자가 해당 화면에 내용을 작성하고 완성시킨 후 메인으로 돌아가는 것으로 일종의 양식(modal)을 작성하는 방식이 적합합니다. 이런 타입의 화면을 나타내기에 적합한 메서드로 modal segue가 있습니다. (*역자 : show타입은 오른쪽으로 이동하는 애니메이션 / modal 타입은 아래에서 위로 새로운 화면이 올라오는 애니메이션)

Attributes inspector를 통해 segue의 스타일을 변경할 수 있습니다(기존의 것을 지우고 새로 생성할 필요는 없습니다)

To change the segue style

  1. meal list scene에서 meal scene으로 가는 segue를 선택하세요

    image
  2. Attributes inspector의 Segue option 팝업 메뉴를 눌러 "Present Modally"를 선택해주세요

  3. Attributes inspector의 identifier 필드에 "AddItem"이라고 입력하고 리턴키를 누르세요. 

    나중에 segue를 식별하기 위해 이 identifier가 필요합니다.

modal 타입의 segue가 적용된 뷰 컨트롤러는 navigation stack에 추가되지 않습니다. 즉, 우리가 작업하는 네비게이션 컨트롤러 범위 내에 있지 않으며, 범위 내의 화면 전환이 아니라, 별도로 화면을 띄우는 것입니다. 따라서 meal list의 네비게이션 컨트롤러가 제공하는 네비게이션 바도 존재하지 않습니다. 하지만 시각적 일관성을 위해서는 meal scene 화면에도 네비게이션 바가 제공되는 것이 좋습니다. modal 방식으로 segue가 구현되는 meal scene에도 네비게이션 바를 제공하기 위해서는 meal scene 자신의 네비게이션 컨트롤러에 삽입되어야 합니다. 

To add a navigation controller to the meal scene

  1. scene dock에서 meal scene을 선택하세요

    image
  2. ViewController가 선택된 상태에서 메뉴바의 Editor > Embed in > Navigation Controller를 클릭하세요

아까와 마찬가지로 navigation controller가 추가 되고 meal scene의 상단에 navigation bar가 나타날 것입니다. navigation bar에 title을 추가하고 Save/Cancel 버튼을 추가하는 작업을 해보겠습니다. 그리고 이 버튼을 액션(action)으로 연결하여 작동하게 만들겠습니다.

image

To configure the navigation bar in the meal scene

  1. meal scene의 navigation bar를 더블클릭하세요

    image

    커서가 나타나고, 텍스트를 입력할 수 있게 됩니다.

  2. "New Meal"이라고 입력하고 리턴키를 눌러 저장하세요

  3. Object library에서 Bar Button Item을 드래그해서 meal scene의 navigation bar의 왼쪽 끝에 추가하세요

  4. Attributes inspector의 System Item메뉴에서 Cancel을 선택하세요(*역자 : Xcode6에서는 identifier메뉴에서 선택하면 됩니다) 

    버튼의 텍스트가 Cancel로 변경됩니다.

    image
  5. 다시 한번 Object library에서 Bar Button Item을 드래그해서 meal scene의 네이게이션 바 오른쪽 끝에 추가해주세요

  6. Attributes inspector의 System Item메뉴에서 Save를 선택해주세요(*역자 : Xcode6에서는 Identifier에서 설정하면 됩니다) 

    버튼의 텍스트가 Save로 변경됩니다.

    image

Checkpoint: 앱을 실행시켜보세요. Add(+)버튼을 클릭해보세요. meal scene이 나타날 것입니다. 하지만 예전처럼 백버튼은 나타나지 않습니다. 대신 우리가 추가한 Cancel, Save 버튼이 나타납니다. 이 두버튼들은 아직 action에 연결되어 있지 않기 때문에 클릭해도 아무런 일도 일어나지 않습니다. save버튼을 누르면 meal을 추가하고, cancel을 누르면 이전 화면인 meal list로 돌아가는 설정 작업은 나중에 해보겠습니다.

image

Finalize the UI with Auto Layout

기본 UI를 만들기 시작한 이래 많은 것이 변했습니다. 더이상 layout에 무언가를 만드는 짓은 그만 두겠습니다. 대신 Auto Layout을 이용해서 모든것이 제대로! 멋지게! 보이도록 해보겠습니다.

그러기 위해선 스택뷰(stack view)에 약간의 조정 작업이 필요합니다.

To update the layout of the stack view

  1. meal scene에서 옅은 파란색 영역을 클릭해서 stack view를 선택해주세요

    image

    outline view에서 stack view를 선택해도 됩니다.

  2. 캔버스의 오른쪽 하단에서 Resolve Auto Layout Issues 메뉴를 클릭하세요

    image
  3. "Selected Views" 그룹 아래에 있는 "Update Constraints"를 선택해주세요

    image

    엘리먼트들은 모두 그대로 있을 것입니다. 하지만 stack view는 더이상 view의 상단 margin에 고정(pinned)되지 않습니다. 대신 navigation bar에 맞게 고정됩니다.

meal scene의 컨스트레인트(constraints)와 UI는 이제 아래 화면과 같습니다.

image

Checkpoint: 앱을 실행시켜보세요. 모든 것이 이전과 동일하게 보일 것입니다.

image

Store New Meals in the Meal List

이제 새로운 음식(meal)을 추가하는 기능을 구현해보겠습니다. 음식의 이름(name), 평가(rating), 사진(photo)을 입력하고 Save버튼을 탭하면 MealViewController가 이 정보를 이용해 Meal 객체를 생성하고, MealTableViewController에게 보냄으로써 음식목록(meal list)에 해당 음식을 노출하도록 작업해야합니다.

먼저 MealViewController에서 Meal 객체를 생성하는 작업부터 시작하겠습니다.

To add a Meal property to MealViewController

  1. MealViewController.swift 파일을 열어주세요

  2. 'ratingControl' 아웃렛 밑에 아래와 같이 프로퍼티를 하나 추가해주세요

    1. /*
    2. This value is either passed by `MealListTableViewController` in `prepareForSegue(_:sender:)`
    3. or constructed as part of adding a new meal.
    4. */
    5. var meal = Meal?()

    MealViewController에 optional 타입의 Meal 프로퍼티를 선언하였습니다. 

    optional 타입은 때에 따라 이 프로퍼티가 nil인 경우가 있음을 명시하는 것입니다.

Save버튼이 탭되었을 때만 Meal객체 만들고 전달하면 됩니다. 따라서 Save버튼이 탭되었는지를 판단하기 위해 save버튼의 outlet을 추가하겠습니다.

To connect the Save button to the MealViewController code

  1. 스토리보드를 열어주세요

  2. 툴바에서 Assistant editor를 열어주세요

    image
  3. 작업공간이 부족하다면 툴바에서 project navigator와 utility area를 숨겨주세요.

    image
  4. 스토리보드에서 Save버튼을 선택하세요

  5. control을 누른채 드래그해서 ratingControl 프로퍼티 아래로 연결하세요

    image
  6. 다이얼로그가 나타나면 Name필드에 "saveButton"이라고 입력하세요. 

    다른 옵션들은 그대로 두세요

    image
  7. Connect를 클릭하세요

이제 코드 상에서 Save 버튼을 참조할 수 있게 되었습니다.

Create an Unwind Segue

Save버튼을 탭했을 때는 Meal객체를 생성하여 MealTableViewController에게 전달해야하며, Cancel버튼을 탭했을 때는 Meal객체를 생성할 필요가 없습니다(optional 타입으로 선언한 이유입니다). 또한 두 경우 모두 작업이 완료되면 음식 목록 화면으로 돌아가야 합니다.(save한 경우엔 새음식 추가된 목록 노출, cancel한 경우엔 이전 목록 그대로 노출)

이 작업을 위해서는 unwind segue를 사용해야합니다. (*역자 : unwind는 '감긴 것을 풀다' '되돌아가다' 등의 의미입니다) unwind segue 는 하나 혹은 그 이상의 segue(화면 전환)을 통해 이전의 뷰컨트롤러 중 하나로 되돌아가는 작업을 수행합니다. 이제 unwind segue를 이용하여  역방향으로 동작하는 네비게이션(reverse navigation)을 구현해보겠습니다.(바로 이전의 화면, 혹은 그 전의 화면 등 네비게이션 스택에 쌓여있는 다른 뷰컨트롤러 화면으로 돌아가는 기능입니다)

화면전환(segue)이 되는 순간 필요한 작업이 있다면 prepareForSegue(_:sender) 메서드를 통해 구현할 수 있습니다. 즉, 음식추가화면에서 음식목록화면으로 이동할 때 필요한 데이터 저장 및 전달 등의 작업은 segue가 시작되는  MealViewController에서 prepareForSegue(_:sender)메서드에 작성하면 됩니다.(segue가 시작되는 뷰컨트롤러를 source view controller라고 하며, segue의 목적지가 되는 뷰컨트롤러를 destination view controller라고 합니다. 따라서 source view controller는 MealViewController이며, destination view controller는 MealTableViewController입니다.)

To implement the prepareForSegue(_:sender:) method on MealViewController

  1. 툴바를 통해 Standard editor로 돌아오세요

    image
  2. MealViewController.swift 를 열어주세요

  3. "// MARK: Actions" 위에 아래의 주석을 추가해주세요

    1. // MARK: Navigation

    이 주석을 통해 이 코드가 네비게이션 기능과 관련된 것임을 쉽게 파악할 수 있습니다.

  4. 주석 밑에 아래의 메서드를 선언해주세요

    1. // This method lets you configure a view controller before it's presented.
    2. override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    3. }
  5. prepareForSegue(_:sender:)메서드 구현부에 아래와 같에 if문을 작성해주세요

    1. if saveButton === sender {
    2. }

    이 코드는 identity operator(===)를 이용해서 파라미터로 들어온 sender가 saveButton outlet이 참조하는 객체와 동일한 것인지 체크합니다. 만약 맞다면, if 구문의 구현부가 실행될 것입니다.

  6. if구문에 아래 코드를 추가해주세요

    1. let name = nameTextField.text ?? ""
    2. let photo = photoImageView.image
    3. let rating = ratingControl.rating

    이 코드는 음식추가화면에 작성한 text field의 텍스트, 추가한 사진, 평가한 점수 저장하기 위한 상수를 선언하고 있습니다. name 상수를 선언하는 부분에 있는 ??(nil coalescing operator)는 optional타입에 사용하는 연산자로, 값이 있으면 언래핑(unwrap)하여 반환하고, 값이 없으면 디폴트로 값을 반환합니다. 여기서는 textField.text(optional - 입력하지 않을 수도 있기 때문)의 값이 있으면 String타입으로 언래핑하여 반환하고, 값이 없으면 디폴트로 지정한 String 타입의 공백("")을 반환합니다.

  7. if 구문에 아래 코드를 추가하세요

    1. // Set the meal to be passed to MealListTableViewController after the unwind segue.
    2. meal = Meal(name: name, photo: photo, rating: rating)

    이 코드는 화면 전환(segue)이 수행되기 전에 사용자가 입력한 정보에 기반한 Meal객체를 생성하여 meal 프로퍼티에 할당하고 있습니다.

완성된 prepareForSegue(_:sender:)메서드의 모습은 아래와 같습니다.

  1. // This method lets you configure a view controller before it's presented.
  2. override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  3. if saveButton === sender {
  4. let name = nameTextField.text ?? ""
  5. let photo = photoImageView.image
  6. let rating = ratingControl.rating
  7. // Set the meal to be passed to MealListTableViewController after the unwind segue.
  8. meal = Meal(name: name, photo: photo, rating: rating)
  9. }
  10. }

unwind segue를 만들기 위한 다음 과정은 segue(화면전환)의 목적지인 destination view controller 에 액션 메서드를 만드는 것입니다. 이 액션 메서드는 UIStoryBoardSegue 이벤트를 받으면 호출되는 메서드로 이 액션 메서드는 IBAction 속성이 추가되어야 하며, segue(UIStoryboardSegue)를 파라미터로 받아야 합니다. meal list로 되돌아와서 필요한 작업을 구현하기 위해서는 이 액션 메서드를 MealTableViewController.swift에 추가해야합니다.(destination view controller는 화면전환(segue) 이 되어 노출될 목적지에 해당하는 뷰컨트롤러입니다. 여기서는 MealTableViewController 입니다)

이 액션 메서드에서 구현해야할 내용은 "MealViewController에서 전달 받은 새로운 Meal을 meal list의 데이터에 추가하고, 테이블 뷰에 노출되도록 행을 추가하는 로직"입니다

To add an action method to MealTableViewController

  1. MealTableViewController.swift 파일을 열어주세요

  2. 코드의 맨마지막 괄호( } ) 바로 앞에 아래 코드를 추가해주세요

    1. @IBAction func unwindToMealList(sender: UIStoryboardSegue) {
    2. }
  3. 액션 메서드에 아래의 if구문을 작성해주세요

    1. if let sourceViewController = sender.sourceViewController as? MealViewController, meal = sourceViewController.meal
    2. }

    if문의 조건부에 사용된 방식은 아주 빈번하게 사용되는 방식입니다.

    파라미터로 전달 받은 seuge의 sourceViewController는 UIViewController타입입니다. 따라서 as?(optional type cast operator)연산자를 이용해 MealViewController로 다운 캐스팅 해야 합니다.

    전달 받은 sender.sourceViewController에 값이 존재하고 다운 캐스팅이 성공하면 지역상수로 선언한 sourceViewController에 다운캐스팅된 MealViewController 객체를 할당하고, if구문을 실행합니다. 하지만 값이 존재하지 않거나 다운캐스팅이 불가한 경우엔 optional(nil)을 반환하고, if구문이 실행되지 않습니다.

    지역 변수 sourceViewController에 정상적으로 MealViewController객체를 할당한 후에는 해당 객체가 보유한 meal프로퍼티가 nil인지를 체크해야합니다. sourceViewController.meal프로퍼티에 Save버튼을 통해 생성한 Meal객체가 정상적으로 들어가 있다면 지역 상수로 선언한 meal에 할당하고 if 구문을 실행합니다.

    만약 sender.sourceViewController의 다운캐스팅에 실패하거나, sourceViewController.meal프로퍼티가 nil인 경우에는 조건부가 false가 되어 if 구문이 실행되지 않습니다.


  4. if문의 구현부에 아래 코드를 추가하세요

    1. // Add a new meal.
    2. let newIndexPath = NSIndexPath(forRow: meals.count, inSection: 0)

    이 코드는 새로 추가한 meal을 노출할 테이블 뷰의 cell 위치를 계산하여 newIndexPath상수에 할당합니다.

  5. 다음 라인에 아래 코드를 작성하세요

    1. meals.append(meal)

    이 코드는 기존 음식의 리스트인 meals 배열에 새로운 meal객체를 추가합니다.

  6. 다음 라인에 아래코드를 작성하세요

    1. tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)

    This animates the addition of a new row to the table view for the cell that contains information about the new meal. The .Bottom animation option shows the inserted row slide in from the bottom. - 이 코드는 새로 만든 meal을 보여줄 행을 테이블 뷰에 추가합니다. UITableViewRowAnimation.Bottom은 새로 추가되는 행이 아래에서 부터 슬라이드 되어 올라오는 애니메이션 효과를 보여줍니다.(Xcode가 이미 알고 있는 enumerate의 경우 .Bottom으로 기술해도 됩니다)

이 메서드를 좀더 고도화하기 위한 수정작업이 필요하지만, 현재까지 완성한 unwindToMealList(_:) 액션 메서드의 모습은 아래와 같습니다.

  1. @IBAction func unwindToMealList(sender: UIStoryboardSegue) {
  2. if let sourceViewController = sender.sourceViewController as? MealViewController, meal = sourceViewController.meal {
  3. // Add a new meal.
  4. let newIndexPath = NSIndexPath(forRow: meals.count, inSection: 0)
  5. meals.append(meal)
  6. tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
  7. }
  8. }

이제 이 액션 메서드를 실제로 실행시킬 unwind segue를 만들어보겠습니다. 

Save버튼을 누르면 unwind segue가 작동하면서 이전화면인 MealTableViewController화면으로 돌아가게 됩니다. 이때 prepareForSegue메서드가 실행되어 segue가 실행되기 전에 필요한 meal생성 작업을 하게 되며, segue가 실행되면 이 segue를 파라미터로 받은 unwindToMealList메서드가 실행되어 테이블뷰에 새로운 행을 추가하고 새 meal의 정보를 노출하게 됩니다.

To link the Save button to the unwindToMealList action method

  1. 스토리보드를 열어주세요

  2. 캔버스에서 Save버튼을 control을 누른채 드래그 하여 meal scene의 상단에 있는 Exit로 연결하세요

    image

    드래그를 마치는 순간 아래와 같은 메뉴가 나타납니다.

    image
  3. Choose unwindToMealList: from the shortcut menu. 메뉴에서 "unwindToMealList:" 를 클릭하세요

    이제 Save버튼을 탭하면 meal list scene 으로 되돌아가게 되며, 그 사이에 unwindToMealList(_:) 액션 메서드가 호출됩니다.

Checkpoint: 앱을 실행해보세요. Add(+)버튼을 클릭하고, 새로운 meal을 만들고, Save 버튼을 클릭하면 meal list에 새로운 meal이 추가됩니다.

Disable Saving When User Doesn't Enter an Item Name

만약 meal의 이름을 입력하지 않고 Save버튼을 누르면 어떤 일이 발생할지 생각해 보시기 바랍니다. MealViewController.swift 에서 meal 프로퍼티는 optional로 선언했으며, Meal.swift에서는 failable initializer를 통해 name값 없이 객체 생성을 시도하면 nil을 반환하고 객체 생성에 실패하도록 구현해두었습니다. 따라서 name값이 없는 경우, Meal객체는 생성되지 않으며 MealTableViewController에서도 다운 캐스팅에 실패하여 새로운 행이 추가 되지 않습니다. 이를 해결하기 위해 name을 입력하지 않은 경우 새로운 meal을 추가하지 못하도록 Save버튼을 비활성화 시키고, 키보드를 내리기 전에는 유효한 이름을 입력했는지 체크하는 로직을 추가하겠습니다.

To disable the Save button when there’s no item name

  1. MealViewController.swift 파일에서 "// MARK: UITextFieldDelegate" 주석 부분을 찾으세요. 

    editor area의 상단에서 functions menu를 이용하면 더 빨리 찾을 수 있습니다.

  2. 주석 밑에 아래와 같은 UITextFieldDelegate메서드를 추가하세요

    1. func textFieldDidBeginEditing(textField: UITextField) {
    2. // Disable the Save button while editing.
    3. saveButton.enabled = false
    4. }

    이 textFieldDidBeginEditing 메서드는 텍스트 필드에 대한 편집(글 입력 등)이 시작되거나 키보드가 올라오면 호출됩니다. 구현부에 자것ㅇ한 saveButton.enabled = false 코드는 텍스트 필드를 편집하는 동안 Save 버튼을 비활성화 시키고 있습니다.

  3. 바로 밑에 아래의 메서드도 추가해주세요

    1. func checkValidMealName() {
    2. // Disable the Save button if the text field is empty.
    3. let text = nameTextField.text ?? ""
    4. saveButton.enabled = !text.isEmpty
    5. }

    text field가 비어있는지를 체크하여 Save버튼의 활성화 여부를 결정하는 일종의 도움 메서드(helper method)입니다.

  4. textFieldDidEndEditing(_:) 메서드를 찾으세요

    1. func textFieldDidEndEditing(textField: UITextField) {
    2. }

    현재 구현부가 비어있을 것입니다.

  5. 아래의 코드를 추가하세요

    1. checkValidMealName()
    2. navigationItem.title = textField.text

    The first line calls checkValidMealName() to check if the text field has text in it, which enables the Save button if it does. The second line sets the title of the scene to that text. - 첫 번째 라인에서 checkValidMealName() 도움 메서드를 호출하여 text filed에 내용이 있는지를 체크하고, Save버튼을 활성화 여부를 결정합니다. 두 번째 라인은 현재 화면의 타이틀을 텍스트 필드에 입력한 값으로 설정합니다.

  6. viewDidLoad() 메서드를 찾아주세요

    1. override func viewDidLoad() {
    2. super.viewDidLoad()
    3. // Handle the text field’s user input through delegate callbacks.
    4. nameTextField.delegate = self
    5. }
  7. checkValidMealName()메서드클 호출하는 코드를 추가해주세요. 화면이 처음 노출된 시점부터 사용자가 유효한 이름을 입력하기 전까지는 Save 버튼을 비활성화 시키게 됩니다.

    1. // Enable the Save button only if the text field has a valid Meal name.
    2. checkValidMealName()

전체 viewDidLoad() 메서드의 모습은 아래와 같습니다.

  1. override func viewDidLoad() {
  2. super.viewDidLoad()
  3. // Handle the text field’s user input through delegate callbacks.
  4. nameTextField.delegate = self
  5. // Enable the Save button only if the text field has a valid Meal name.
  6. checkValidMealName()
  7. }

완성된 textFieldDidEndEditing(_)메서드의 모습은 아래와 같습니다.

  1. func textFieldDidEndEditing(textField: UITextField) {
  2. checkValidMealName()
  3. navigationItem.title = textField.text
  4. }

Checkpoint: Run your app. Now when you click the Add button (+), the Save button is disabled until you enter a valid (nonempty) meal name and dismiss the keyboard. - 앱을 실행시켜보세요. Add(+)버튼을 클릭하면 음식 추가 화면인 MealViewController가 나타납니다. 유효한 이름을 입력하고 키보드를 내리기 전까지는 Save 버튼이 활성화되지 않습니다.

image

Cancel a New Meal Addition

사용자는 새로운 음식을 추가하려다가 취소해버릴 수도 있습니다. 이 경우에는 아무것도 저장하지 않고 다시 meal list로 돌아와야 합니다. 이를 위해 Cancel버튼의 동작도 구현을 해보겠습니다.

To create and implement a cancel action method

  1. 스토리보드를 열어주세요

  2. 툴바를 통해 Assistant editor를 열어주세요

    image
  3. In your storyboard, select the Cancel button. - 스토리보드에서 Cancel 버튼을 선택해주세요

  4. control을 누른 채 드래그하여 MealViewController.swift파일의 "// MARK: Navigation" 주석 아래로 연결해주세요

    image
  5. 다이얼로그가 나오면 Connection 부분을 Action으로 선택해주세요

  6. Name은 "cancel"을 입력해주세요

  7. Type에서는 "UIBarButtonItem"을 선택해주세요

    나머지 옵션은 그대로 두세요

    image
  8. Connect를 클릭하세요

    MealViewController.swift 파일에 아래의 액션이 추가됩니다.

    1. @IBAction func cancel(sender: UIBarButtonItem) {
    2. }
  9. In the cancel(_:) action method, add the following line of code:  - cancel(_:) 액션 메서드에 아래와 같은 라인을 추가해주세요

    1. dismissViewControllerAnimated(true, completion: nil)

    이 코드는 어떠한 정보도 저장하지 않고 meal scene을 없애버립니다. meal scene 이 사라지고 나면 meal list가 나타납니다.(meal scene이 meal list를 덮고 있는 형태였기 때문입니다)

완성된 cancel(_:) 메서드의 모습은 아래와 같습니다.

  1. @IBAction func cancel(sender: UIBarButtonItem) {
  2. dismissViewControllerAnimated(true, completion: nil)
  3. }

Checkpoint: 앱을 실행해보세요. Add(+) 버튼을 클릭하여 화면이 전환된 뒤, Save 버튼 대신 Cancel버튼을 눌러보세요. 새로운 meal을 추가하지 않고, meal list로 되돌아 오게 됩니다.


댓글