스톱 워치 만들기 (1) : https://wiwi-pe.tistory.com/34

스톱워치 만들기 (2) : https://wiwi-pe.tistory.com/35

스톱워치 만들기 (3) : https://wiwi-pe.tistory.com/36

스톱워치 만들기 (4) : https://wiwi-pe.tistory.com/37

스톱워치 만들기 (5) : https://wiwi-pe.tistory.com/38

스톱워치 만들기 (6) : https://wiwi-pe.tistory.com/39

스톱워치 만들기 (7) : https://wiwi-pe.tistory.com/40

스톱워치 만들기 (8) : https://wiwi-pe.tistory.com/41

 

iOS 개발 튜토리얼1 - 스톱워치 만들기 (8) 타이머

스톱 워치 만들기 (1) : https://wiwi-pe.tistory.com/34 스톱워치 만들기 (2) : https://wiwi-pe.tistory.com/35 스톱워치 만들기 (3) : https://wiwi-pe.tistory.com/36 스톱워치 만들기 (4) : https://wiwi-pe...

wiwi-pe.tistory.com

 

드디어 이 튜토리얼 앱의 핵심기능인 랩타임을 남기는기능을 작성할겁니다.

 

앱 가운데 줄만 그어지고 코드도 단 한줄만 작성해본 테이블뷰를 다룰거에요

 

들어가기전 아래 링크에 들어가셔서 읽고 시작하심을 추천드립니다

아니, 꼭! 들어가셔서 읽어보세요

https://developer.apple.com/documentation/uikit/uitableview

 

UITableView - UIKit | Apple Developer Documentation

Methods for managing selections, configuring section headers and footers, deleting and reordering cells, and performing other actions in a table view.

developer.apple.com

 

이 테이블뷰는 단일행을 여러개 스크롤형식으로 보여주는 장치입니다.

이 행하나를 셀, 이라고 부르구요 이 셀들을 따로 작성을하고

이 셀을 재사용하면서 계속해서 보여지고, 초기화되고 하는 놀라운 모습을 보여줍니다

 

실제로는 어디까지 관리되는지는 모르겠습니다만 대략 아래와 같은 형태로 테이블뷰는 동작합니다.

 

A라는 모양을 가진 셀을 데이터만 다르게 작성하여 어느한 배열에 넣어줍니다

swift에서는 배열을 아래와같이 만들어줍니다

 

var tableData = [String]()

내용을 바로바로 적어주는게아니라 하나하나 "추가" 해줄것이기에 let이 아닌 var로 하고

String, 문자열 배열입니다

 

임시로 몇개 데이터를 넣습니다

tableData.append("홍길동")

tableData.append("홍길순")

tableData.append("홍길연")

tableData.append("홍길호")

tableData.append("홍길정")

tableData.append("홍길수")

 

홍씨가문 일가입니다  ㅎㅎ

 

아래는 이 데이터를바탕으로 테이블뷰가 보여지는 방식입니다

 

0행 A셀: 홍길순

------실제로 보여지는 영역 ↓-------

1행 A셀: 홍길연

2행 A셀: 홍길호

------실제로 보여지는 영역-------

3행 A셀: 홍길정

 

실제로 보여지는 행은 단 두가지 행이고 이제 위 아래로 사용자가 스크롤할것을 대비해 미리 로드해둡니다.

실제 데이터는 길동이부터 길수까지 6개 이지만 화면에 보이는두개와 미리 로드해둔 두개, 실제로 사용하고있는건 4개뿐이라는거죠.

 

미리로드되는수는 다를 수 있습니다.

 

데이터가 어느정도 많아도 결국 실제로 다루는건 몇개가 안되기에 빠른속도로 데이터를 활용할 수 있습니다.

같은셀을 저 데이터수만큼 준비를 안해도 된다는거죠.

하나의 셀을 재사용한다는게 중요합니다.

여러개의 다른 셀을 재사용할 수 도 있어요

 

그럼 작업을 해봅시다

@IBOutlet weak var lapTableView: UITableView!

아래에 이 테이블뷰의 데이터가될 배열을 생성해주세요

var lapTableviewData = [String]()

 

override func viewDidLoad() {

}

함수 안의

 

super.viewDidLoad()

아래 라인에 다음과같이 2라인을 추가해주세요

 

lapTableView.delegate = self

lapTableView.dataSource = self

 

delegate와 dataSource가 어디에서 관리하는지를 설정하는겁니다

iOS개발에 더 공부하고싶다면, 델리게이트에관한 자세한내용은 델리게이트패턴, 위임패턴 이런식으로 검색하셔서 더 알아보시는걸 추천드립니다.

이 튜토리얼에선 그냥 이렇게 사용한다라는건만 다루겠습니다.

 

이렇게 넣고 보면 

Cannot assign value of type 'MainVC' to type 'UITableViewDelegate?'

라며 빨간줄이 뜰겁니다

그리고 자세히보기를 누르면  이에대한 수정방안으로 

Insert ' as! UITableViewDelegate'

self뒤에 as! UITableViewDelegate 를 붙여주겠느냐 라고 묻습니다

당연히! 안됩니다...

 

self, 현 클래스를 UITableviewDelegate로 변환해 설정하겠다는의미인데 현 클래스는 UITableviewDelegate가 아닙니다;;

 

여기서 swift의 강력한 키워드중하나인 extension을 쓸겁니다

class MainVC: UIViewController { } 가 닫아지는 바로 아래라인에

extension MainVC : UITableViewDelegate, UITableViewDataSource {

    

}

라고 붙여줍시다.

 

그러면 MainVC.swift 파일은 아래와같은 형태가 됩니다

 

import UIKit

class MainVC: UIViewController {
	~~~~
}
extension MainVC : UITableViewDelegate, UITableViewDataSource {
    
}

 

 

이 extension이 뭐냐하면 바로 클래스의 확장입니다,

이미 만들어진 무언가를 이 키워드를 통해 상속, 기능추가 다양한일을 할 수 있습니다.

이 아래 extnesion 부분을 다른 파일에다가 작성해도 동작합니다 ㅎ 

즉 만약 String 클래스에 어떠한 특수한 기능을 넣고싶다?

extension String {

 fucn 특수함수(){}

}

이런식으로 선언하고

다른곳에서 String.특수함수() 이렇게 사용 할 수 있게됩니다!

 

기존 기본 클래스들에 다양한 바리에이션을 넣어 개발자 맘대로 바꿀수 있어요

한프로젝트 내에서만 통하니 설사 이상한게 들어가도 다른프로젝트에 영향이 가지않습니다 ㅎ

 

그렇게 MainVC에 extension 키워드로 UITableViewDelegate와 UITableViewDataSource를 붙여 확장 시켜준겁니다. 이에대해서는 상속과 프로토콜에대해 공부하시면 더욱 자세히 알 수 있어요

 

이렇게 해놓으면 또 xcode에서 태클이 들어옵니다

xcode왈

Type 'MainVC' does not conform to protocol 'UITableViewDataSource'

 

그리고 자세히보기를 누르면

Do you want to add protocol stubs?

라고 뜹니다

 

이는 꼭 필수로 넣어줘야한다고 지정해준것들이 추가가 안되어있어서 뜨는 에러입니다

 

이번에는 위에서 처럼 무시하지말고 Fix를 눌러줍시다

그럼 놀랍게도

이 내부안에 아래와같이 

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        <#code#>
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        <#code#>
    }
}

자동으로 채워지게됩니다.

정말이지 환타스틱한 기능이에요

 

우선

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { }

함수먼저 채워줄거에요

함수 형식을보면 Int값을 반환해줘야하죠?

그리고 함수 파라미터를 잘 읽어보면

number of rows in section 이라고 합니다

하나의 섹션, 구역에 몇줄이냐 라고 지정해주는겁니다

 

사실 테이블뷰는 여러섹션으로 나눠줄수 있어요 

위에서 UITableview에 대한 애플도큐먼트 문서를 읽어보셨으면 아아! 라고 하실만한 내용인데

이 섹션을 나눠서 나중에는 여러배열을 넣어주거나 각 섹션에 이름을 붙여줘 깔끔하게 보여지게 할 수 도 있습니다 만

이곳에서는 안할겁니다

 

그러니 단 하나의 섹션만 사용할거고

이에대한 총 줄개수는

lapTableviewData 배열의 아이템 개수입니다 ㅎ

 

그러니 아래와 같이 변경해줍시다

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

배열의 아이템개수는 뒤에 count를 붙이시면 Int형태로 반환됩니다 ㅎ 

 

이제 그다음은 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}

인데 

파라미터를 잘 읽어보면

cell for row at indexPath라고 적혀있죠?

indexPath가 뭔지는 모르겠지만 이에 대한 한 줄의 셀에 대해라고합니다

그리고 이 함수의 반환타입은 UITableVeiwCell입니다, 

뒤에 셀이 붙어있죠?

 

위에서 테이블뷰 설명에서 

"이 행하나를 셀, 이라고 부르구요" 라고 했죠? 하나의 줄에 들어갈 아이템 셀 을 이곳에서 지정해주는구나! 라고 짐작하실수 있습니다 

indexPath는 이에대한 위치정보를 담고 있는 녀석인데

현재 보고있는 다루고있는 섹션과 행에 대한 파라미터입니다

 

우리는 섹션을 오로지 하나만 사용하니 이곳에서는 그냥 줄의 인덱스겠네요?

 

바로 이 셀에대해 적어주고싶지만 아쉽게도 우리는 셀의UI를 작성하지않았습니다 ㅠㅠ

 

UI를 작성하려면 다시 스토리보드로 돌아가야겠네요

 

스토리보드다룰때 UI를 완성했습니다아! 라고 했는데 거짓말이 되어버려서 참 죄송합니다.

 

프로젝트네이비게이션에서 Main.storyboard로 들어가줍시다.

 

그리고 테이블뷰를 선택해주시고 + 버튼 혹은 shift + command + l 버튼으로 오브젝트하나를 추가해줄거에요

tableviewCell을 찾으셔서 넣어줍시다

 

하나를 추가해주면  아래와같이 됩니다 ㅎ

 

이 추가된 cell에 저희가 랩타임을 기록할 화면을 만들어줄거에요

UI구성에대해서는 초기 화면 구성처럼 자세히 하지는 않고 빠르게 넘기겠습니다

이미지뷰와 라벨을 아래와같이 배치해주세요

 

이미지뷰에는 Vertically in Container를 0으로 넣어주시고

leading을 Constrain to margins를 체크해주고 0으로 넣어주세요

그리고 width와 height를 40씩으로 고정해주세요

 

그다음 라벨은 Constrain to margins를 체크해준 상태로

top 8

leading 8

bottom 8

trailing 0 으로 추가해주세요

 

그다음 cell을 눌러주시고 (content view가 아닙니다)

 

 

어트리뷰트 인스펙터를 봐주세요 

여기서 identifier를 추가해줄겁니다

hint로 Reuse Identifier라고 적어있죠?

위에서 테이블뷰는 같은셀을 여러번 재사용한다고 알려드렸죠? 

그리고 재사용되는 셀은 여러개의 셀을 쓸수도 있다고 말씀드렸는데 

그러한 연유로 이 셀을 구분할 Id가 필요합니다 이를 여기서 지정해줄거에요

 

cell_id_lap 이라고 적어줍시다

 

//////

이미지뷰에 이미지를 넣는것을 잊고있었네요

아래 링크에서 Alarm On 을 투명한 버전으로 다운받읍시다

https://material.io/resources/icons/?style=baseline

 

Resources

Build beautiful, usable products faster. Material Design is an adaptable system—backed by open-source code—that helps teams build high quality digital experiences.

material.io

또는 첨부된 파일을 다운로드해주세요 

 

alarm_on-white-ios.zip
0.02MB

 

압축을 푸시면 

아래와같이 내용물이있는데

사실 이것을 프로젝트의 Aset폴더로 이동하여 바로 추가가 가능하지만 

프로젝트 내부에서 추가해봅시다

우선 애들 파일이름을 다음과같이 변경해주세요

이름뒤에 @붙은것은 이 이미지의 배율과같다고 생각하시면됩니다

실제 사이즈는 몇인데 이를 몇배 작개표현해 이미지의 해상도를 올리는겁니다

 

프로젝트네비게이션에서 Assets.xcassets로 들어가보세요 그리고 Appicon아래쪽 공간에서 Import를 눌러주세요

 

아까 이름바꾼 파일 3개를 선택해서 추가해주세요

그럼 아래와 같이 나와야합니다 ㅎ

 

옆의 인스펙터즈 창을열고 어트리뷰트 인스펙터로 들어가 Render as 부분을(보라색 테두리친부분) 아래사진과같이 Templete Image로 변경해줍니다

이미지 이름이 변경되었습니다 lap_white -> lap_clear

 

다시 Main,storyboard로 돌아가서

셀의 이미지뷰의 어트리뷰트 인스펙터의 이미지를 아까 추가해준 lap_clear로 해줍시다.

 

///////////

이전 메인화면에대한 UI를 만들고 코드로 연결해줬는데

이건 MainVC에 연결하려고해도 안될겁니다 

 

사실 이 셀은 같은 파일에있지만 다른 뷰에있는것이기에 새롭게 클래스를 만들어줘야합니다 ㅠ

 

프로젝트 네비게이션에서 Tutorial1 폴더에서 newfile을 추가해줍시다

Cocoa touch Class를 선택하고 Next 를하며

아래 사진과같은 클래스이름을 지정해줍니다

 

서브클래스를 UITableViewCell로 지정하시는걸 잊지마세요!

 

그러면 MainTableviewCell이라는 파일이 생성될것이고 들어가보면 

아래와같은 소스가 자동으로 입력되어있을겁니다

import UIKit

class MainTableviewCell: UITableViewCell {

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

이를 아래와같이 변경해주세요

import UIKit

class MainTableviewCell: UITableViewCell {
    static let cell_id = "cell_id_lap"
    
    override func prepareForReuse() {
        super.prepareForReuse()
    }
}

 

awakeFromNib는 이 셀이 생성될때의 불러지는 함수이고

테이블뷰에서 셀을 선택했을때 돌아간다는 설명이 붙어있지만 우리는 이 둘다 사용안할겁니다.

prepareForReuse함수는 이 셀을 재사용할때지정해주는 불려지는 함수입니다.

 

기본적인 셀에대한 클래스는 만들었으니 이제 연결해줍시다.

아까 셀을 만든 Main.storboard화면으로 돌아가서 

셀을 누르시고(위의 id를지정해줬을때랑 같이 셀을 누르셔야합니다.)

아이덴티티 인스펙터화면에서 Class를 방금 만든 클래스이름을 넣어줍니다.

그리고

 

이전에 UI와 코드를 연결해준것처럼 Add editor on Right 라는 설명이나오는 에디터화면추가 버튼을 눌러서 코드와 스토리보드화면을 동시에 띄여줘서 아래와같이 셀에있는 이미지뷰와 라벨을 코드에 연결해줍니다

그후 prepareForReuse함수에 한줄을 추가해주세요 

timeLabel.text = ""

 

이렇게 하면 이제 이 셀에대한 기본적인 설정은 끝났습니다

셀의 전체 코드는 아래입니다

import UIKit

class MainTableviewCell: UITableViewCell {
    static let cell_id = "cell_id_lap"
    
    @IBOutlet weak var timeImageView: UIImageView!
    @IBOutlet weak var timeLabel: UILabel!
    
    override func prepareForReuse() {
        super.prepareForReuse()
        timeLabel.text = ""
    }
}

 

 

 

 

이제 다시 MainVC로 돌아가서 

아까 작성안한 

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {} 

을 채워줄겁니다

아래와같이 해주세요

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: MainTableviewCell.cell_id, for: indexPath) as! MainTableviewCell
        let item = self.lapTableviewData[indexPath.row]
        cell.timeLabel.text = item
        return cell
    }

tableview의 재사용할 셀을 생성해주고 

let cell = tableView.dequeueReusableCell(withIdentifier: MainTableviewCell.cell_id, for: indexPath) 

이를 아까 만든 MainTableviewCell로 강제변환해주는겁니다

as! MainTableviewCell

 

그리고 indexPath에 있는 줄정보로 이 테이블데이터를 뽑아와

let item = self.lapTableviewData[indexPath.row]

 

cell에 있는 타임라벨의 텍스트를 지정해줍니다.

cell.timeLabel.text = item

 

그리고 이 셀을 반환해주면

return cell

이 앱에서의 테이블뷰의 보여주는쪽의 설정은 거의 끝났습니다 

 

checkAction을 채워줄 차례입니다 ㅎ

아래와같이 변경해주세요

 

    func checkAction() {
        print("check")
        let timeString = self.makeTimeLabel(count: self.timeCount)
        self.lapTableviewData.append("\(timeString.0).\(timeString.1)")
        let indexPath = IndexPath(row: self.lapTableviewData.count - 1, section: 0)
        self.lapTableView.insertRows(at: [indexPath], with: .automatic)
        self.lapTableView.scrollToRow(at: indexPath, at: .none, animated: true)
    }
    

체크 버튼을 누르면

이전에 만들어둔 makeTimeLabel로 현재 카운트에대한 시간텍스트를 두개 생성하여 

let timeString = self.makeTimeLabel(count: self.timeCount)

 

lapTableviewData에 넣어줍니다.

self.lapTableviewData.append("\(timeString.0).\(timeString.1)")

 

그리고 넣어준 테이블데이터의 개수로 이게 몇번째 줄에 들어갈지에대한 인덱스, indexPath를 설정해줍니다.

let indexPath = IndexPath(row: self.lapTableviewData.count - 1, section: 0)

 

그리고 이 테이블뷰에 indexPath의 위치로 줄을 애니메이션을 넣어 추가해줍니다

self.lapTableView.insertRows(at: [indexPath], with: .automatic)

 

with부분이 애니메이션부분인데 이에대해서는 xcode에서 제공해주는 설명을 읽어보시기바랍니다 ㅎ

사실 추가하는걸 이렇게 안해도됩니다

indexPath도 생성안하고 테이블데이터에 넣은 후 바로 lapTableView.reloadData()라고 적으시면 테이블뷰 전체가 다시 그려집니다

이렇게 해준 이유는 값이 부드럽게 넣어지라고 해준겁니다

전체가 다시 그려지는것보단 아래에서 부드럽게 추가되는게 보기좋아서 이렇게준거에요

그냥 제취향입니다.

 

그후 테이블뷰의 스크롤을 아까 만든 indexPath위치로 당깁니다

self.lapTableView.scrollToRow(at: indexPath, at: .none, animated: true)

물론 애니메이션을 넣어서 말이죠 

 

 

그리고 이제 resetAction을 수정해줍시다

이전에는 테이블에대한 내용이없어서 그내용을 추가해줄거에요

아래와같이 변경해줍시다

    func resetAction() {
        mainTimer?.invalidate()
        mainTimer = nil
        timeCount = 0
        timeLabel.text = "00:00"
        decimalLabel.text = ".0"
        self.lapTableviewData.removeAll()
        self.lapTableView.reloadData()
        stopButton.isEnabled = false
        checkButton.isEnabled = false
        startButton.isEnabled = true
    }

테이블데이터가 담긴 배열을 전부다 비우고

self.lapTableviewData.removeAll()

 

테이블을 다시 그려줍니다.

self.lapTableView.reloadData()

 

그럼 실행해볼까요?

 

 

색이라던가 테이블뷰모습이 처음 원했던 모습이 아닙니다.

쓰다보니 조금 힘드네요 이런 부분은 다음편에서 다루겠습니다.

 

 

+ Recent posts