튜토리얼: iOS 앱 처음부터 만들기

 

첫 Realm 플랫폼 iOS 앱 만들기

이 튜토리얼은 RealmTasks 데모 앱과 동기화하는 iOS 앱을 Realm Swift로 만드는 튜토리얼입니다.

먼저, 소개 섹션에서 Realm 플랫폼을 설정하고 RealmTasks macOS 버전을 실행하세요.

1. 새 Xcode 프로젝트 생성

  1. Xcode 8 실행
  2. “Create a new Xcode project” 클릭
  3. “iOS”, “Application”, “Single View Application”를 차례대로 선택 후 “Next” 클릭
  4. “Product Name” 항목에 “RealmTasksTutorial” 입력
  5. “Language” 드롭다운 메뉴에서 “Swift” 선택
  6. “Devices” 드롭다운 메뉴에서 “iPhone” 선택
  7. 팀 이름을 선택(필요 시 Xcode preference에서 로그인) 후 조직 이름 입력
  8. “Next” 클릭 후 Mac에서 프로젝트를 생성할 위치를 선택한 다음 “Create” 클릭

2. 코드 베이스 단순화를 위해 Xcode 프로젝트의 일부 제거하기

  1. Xcode 프로젝트 내비게이터에서 Main.storyboard 파일 삭제 후 프롬프트 창에서 “Move To Trash” 클릭
  2. RealmTasksTutorial 타겟 에디터의 “General” 탭에서 “Deployment Info” 섹션의 “Main Interface” 텍스트 항목 삭제
  3. Also on the “General” 탭에서 조직 이름과 팀 이름 선택 (Xcode의 Preferences/Account 창에서 Apple 개발자 계정으로 로그인이 필요할 수 있음)
  4. “Capabilities” 창으로 이동해서 “Keychain Sharing” 스위치 켜기

ViewController.swift 파일의 내용을 지우고 아래 내용으로 바꾸세요.

import UIKit

class ViewController: UITableViewController {
}

다음으로 AppDelegate.swift의 내용을 아래와 같이 바꿉니다.

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:[UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = UINavigationController(rootViewController: ViewController(style: .plain))
        window?.makeKeyAndVisible()
        return true
    }
}

소스 컨트롤에서 진행 내용을 커밋하세요. 선택 사항입니다.

빈 화면이 보이겠지만 이 단계에서 앱을 빌드하고 실행할 수 있습니다.

3. Realm Swift import와 모델 생성

Realm 플랫폼의 최신 Swift 버전을 다운로드 받으세요. 압축파일을 풀고 realm-swift-*.*.* 디렉토리를 엽니다. ios를 연 후 Xcode 8.1을 사용하면 swift-3.0.1, Xcode 8.1을 사용하면 swift-3.0 디렉토리를 여세요.

다운로드 디렉토리에서 Realm 플랫폼 디렉토리를 열고 SDKs/realm-cocoa 디렉토리에서 ios, swift-3.0 디렉토리로 차례로 들어가세요.

Realm.frameworkRealmSwift.framework를 “General” 탭의 RealmTasksTutorial 타겟의 “Embedded Binaries” 섹션으로 드래그합니다.

확인 대화상자가 나오면 “Copy Items if Needed”를 체크하고 “Finish”를 클릭합니다.

다음으로 ViewController.swift의 상단에 다음과 같이 입력합니다. (새 import 선언을 기존의 import UIKit 아래에 넣으세요.)

import RealmSwift

// MARK: Model

final class TaskList: Object {
    dynamic var text = ""
    dynamic var id = ""
    let items = List<Task>()

    override static func primaryKey() -> String? {
        return "id"
    }
}

final class Task: Object {
    dynamic var text = ""
    dynamic var completed = false
}

여기까지 앱에 RealmSwift를 import하고 TaskTaskList 데이터 모델을 정의했습니다. 이제 우리 데이터를 보여 주고 RealmTasks 앱과 동기화하겠습니다.

앱은 빌드 및 실행 상태로 있어야 합니다.

4. 타이틀 입력 및 테이블뷰에 사용할 셀 등록

ViewController 클래스에 다음과 같이 추가합니다.

override func viewDidLoad() {
    super.viewDidLoad()
    setupUI()
}

func setupUI() {
    title = "My Tasks"
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}

5. Realm 리스트를 사용해서 테이블뷰에 태스크 표시

클래스 선언부 바로 아래에 새 줄로 ViewController 클래스에 다음 프로퍼티를 추가합니다.

var items = List<Task>()

viewDidLoad() 함수의 끝부분에 다음처럼 입력해서 리스트의 초기 데이터를 만듭니다.

items.append(Task(value: ["text": "My First Task"]))

ViewController 클래스 바디의 끝부분에 다음 코드를 추가합니다.

// MARK: UITableView

override func tableView(_ tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
    return items.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    let item = items[indexPath.row]
    cell.textLabel?.text = item.text
    cell.textLabel?.alpha = item.completed ? 0.5 : 1
    return cell
}

앱을 빌드하고 실행하면 테이블에 하나의 태스크가 표시되는 것을 볼 수 있습니다. 위 코드에서는 완료된 아이템이 완료되지 않은 아이템보다 밝게 표시되는 기능이 있지만 아직까지는 확인해볼 수 없습니다.

6. 새 태스크 생성 기능

5번 단계에서 추가한 items.append(Task(value: ["text": "My First Task"])) 코드를 지우세요.

다음 함수를 ViewController 클래스 바디의 하단에 정의합니다.

// MARK: Functions

func add() {
    let alertController = UIAlertController(title: "New Task", message: "Enter Task Name", preferredStyle: .alert)
    var alertTextField: UITextField!
    alertController.addTextField { textField in
        alertTextField = textField
        textField.placeholder = "Task Name"
    }
    alertController.addAction(UIAlertAction(title: "Add", style: .default) { _ in
        guard let text = alertTextField.text , !text.isEmpty else { return }

		self.items.append(Task(value: ["text": text]))
		self.tableView.reloadData()
    })
    present(alertController, animated: true, completion: nil)
}

setupUI() 함수의 맨 마지막에 다음 코드를 추가합니다.

navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add))

7. Realm에 아이템 저장 및 동기화 통합

ViewController 클래스에 다음 프로퍼티를 추가합니다. items 프로퍼티 아래에 추가하면 됩니다.

var notificationToken: NotificationToken!
var realm: Realm!

방금 추가한 노티피케이션 토큰은 Realm의 변화를 추적할 때 필요합니다.


setupUI() 함수 다음에 아래 코드를 추가하세요.

func setupRealm() {
    // Log in existing user with username and password
    let username = "test"  // <--- Update this
    let password = "test"  // <--- Update this
}

deinit {
    notificationToken.stop()
}

위의 코드에서 RealmTasks의 “소개” 섹션에서 사용자 등록에 사용했던 사용자 이름과 비밀번호를 usernamepassword 변수에 넣어주세요.

다음으로 아래 코드를 setupRealm() 함수 아래에 추가합니다.

SyncUser.logIn(with: .usernamePassword(username: username, password: password, register: false), server: URL(string: "http://127.0.0.1:9080")!) { user, error in
    guard let user = user else {
        fatalError(String(describing: error))
    }
    
    DispatchQueue.main.async {
        // Open Realm
        let configuration = Realm.Configuration(
            syncConfiguration: SyncConfiguration(user: user, realmURL: URL(string: "realm://127.0.0.1:9080/~/realmtasks")!)
        )
        self.realm = try! Realm(configuration: configuration)
        
        // Show initial tasks
        func updateList() {
            if self.items.realm == nil, let list = self.realm.objects(TaskList.self).first {
                self.items = list.items
            }
            self.tableView.reloadData()
        }
        updateList()
        
        // Notify us when Realm changes
        self.notificationToken = self.realm.addNotificationBlock { _ in
            updateList()
        }
    }
}

setupRealm() 호출을 위한 코드를 viewDidLoad() 메서드 맨 끝에 추가합니다. 중괄호 안에 넣어야 합니다.


이제 아래 코드를 add()에서 삭제합니다.

self.items.append(Task(value: ["text": text]))
self.tableView.reloadData()

그 부분을 다음 코드로 교체합니다. Realm에 새 태스크를 쓰는 역할입니다.

let items = self.items
try! items.realm?.write {
    items.insert(Task(value: ["text": text]), at: items.filter("completed = false").count)
}

non-TLS 네트워크 리퀘스트를 허용해서 로컬 동기화 서버와 통신할 수 있도록 해야 합니다.

Info.plist 파일을 우클릭하고 “Open as… Source Code”를 선택한 후 <dict> 섹션에 아래 코드를 추가합니다.

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

이 시점에서 앱을 빌드하고 실행하면 오브젝트 서버와 연결되고 이전에 RealmTasks에 추가했던 태스크를 표시합니다.

“Add” 버튼을 눌러 새 태스크를 추가했다면 RealmTasks 앱에도 같은 내용이 추가되는 것을 볼 수 있습니다.

축하합니다! 첫 동기화 Realm 앱을 빌드했습니다!

보다 많은 기능을 얼마나 쉽게 추가할 수 있는지 확인하고 태스크 관리 앱을 끝까지 완성하려면 계속 진행하세요.

8. 태스크 이동 및 삭제 기능

setupUI() 함수의 마지막에 아래 코드를 추가합니다.

navigationItem.leftBarButtonItem = editButtonItem

ViewController 클래스 바디의 테이블 섹션에 아래 함수를 구현합니다.

override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    try! items.realm?.write {
        items.move(from: sourceIndexPath.row, to: destinationIndexPath.row)
    }
}

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
          try! realm.write {
              let item = items[indexPath.row]
              realm.delete(item)
          }
    }
}

9. 태스크 탭으로 ‘completed’ 상태 토글 기능

ViewController 클래스의 테이블뷰 섹션 마지막 부분에서 다음 함수를 오버라이드합니다.

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let item = items[indexPath.row]
    try! item.realm?.write {
        item.completed = !item.completed
        let destinationIndexPath: IndexPath
        if item.completed {
            // move cell to bottom
            destinationIndexPath = IndexPath(row: items.count - 1, section: 0)
        } else {
            // move cell just above the first completed item
            let completedCount = items.filter("completed = true").count
            destinationIndexPath = IndexPath(row: items.count - completedCount - 1, section: 0)
        }
        items.move(from: indexPath.row, to: destinationIndexPath.row)
    }
}

10. 완성!

이제 데이터가 실시간으로 동기화되는 기본적인 태스크 관리 앱을 모두 완성했습니다!