[Swift] ViewController로 메모 앱 만들기(3) : UIAlertController, UITableViewCell

Oni·2023년 9월 4일
0

TIL

목록 보기
20/47
post-thumbnail

원문 포스팅 🔗

UIAlertController

어제 작업한 내용을 이어받아 [할 일 확인하기] 페이지의 오른쪽 상단 addList 를 눌러 메모(할 일)을 추가했다.
IBAction 을 연결하고 아래와 같이 코드를 작성했다.

Swift에는 UIAlertController라는 클래스가 있는데, 사용자에게 경고 메시지를 보여주거나 확인이 필요한 경우에 알림 창을 띄워주는 기능이다.

다음은 alert를 생성하고, OK 버튼을 누를 때의 동작을 설정하는 예제이다.

// alert 인스턴스 생성(title: 제목, message: 표시할 메세지, preferred: 스타일)
let alert = UIAlertController(title: "My Alert", message: "This is an alert.", preferredStyle: .alert) 

// OK 버튼 생성(handler: 버튼을 눌렀을 때 동작을 설정하는 클로저(Closure))
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"), 
                              style: .default, 
                              // The "OK" alert occurred 메세지 출력
                              handler: { _ in NSLog("The \"OK\" alert occured.")}))
              
// 뷰 컨트롤러에 표시(completion 클로저: Alert 표시 이후 실행될 추가적인 동작 설정 가능)
self.present(alert, animated: true, completion: nil)

여기서 alert의 스타일은 크게 alert와 actionSheet가 있는데, actionSheet는 리스트 형태로 목록만 보여주기 때문에 입력값(textField)을 받을 수 있는 alert와 선택적으로 사용하면 된다.

해당 예제를 활용해서 내 코드를 작성했다.
할 일 추가라는 타이틀을 가진 alert를 생성하고 기본 메세지는 없다. 그리고 텍스트필드에 "내용을 입력하세요." 라는 문구를 추가해줬다.

// 인스턴스 생성
let alert = UIAlertController(title: "할 일 추가", message: "", preferredStyle: .alert)
    
// Alert textField 작성 가이드
alert.addTextField() { (tf) in
    tf.placeholder = "내용을 입력하세요."
}

그리고 확인버튼과 취소버튼을 생성해야 하는데, 해당 버튼을 눌렀을 때 어떤 동작을 하는지 트레일링 클로저를 통해 설정해줬다.
입력값을 Memo라는 클래스에 추가를 해줘야 하니까 텍스트필드에 입력값이 존재할 때 그 입력값을 newMemo라는 객체를 생성해서 append 해줬다.
확인을 누르면 해당 페이지에서 내용이 보여져야 하니까 reloadData 를 추가했다. 취소의 경우 별다른 액션이 없어 handler는 nil로 줬다.

// 확인 버튼 객체 생성
let ok = UIAlertAction(title: "확인", style: .default) { (_) in
    // 내용을 입력했을 때
    if let memoTitle = alert.textFields?[0].text {
        // 입력값을 이용하여 Memo 객체 생성
        let newMemo = Memo(content: memoTitle)
        // Memo 객체를 MemoList 배열에 추가
        Memo.MemoList.append(newMemo)
        self.tableView.reloadData()
    }
}

// 취소 버튼 객체 생성
let cancel = UIAlertAction(title: "취소", style: .cancel, handler: nil)

버튼 생성이 끝나면 해당 버튼을 Alert 에 등록하고 화면에 띄워주는 코드까지 작성하면 된다.

// Alert에 버튼 객체 등록
alert.addAction(ok)
alert.addAction(cancel)

// Alert 띄우기
self.present(alert, animated: false)

이제 numberOfRowsInSection와 cellForRowAt를 이용해서 테이블뷰에 작성된 데이터들을 불러온다.

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

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

    // TableViewCell 클래스에서 정의한 셀로 적용
    let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
    
    let target = Memo.MemoList[indexPath.row]
    cell.textLabel?.text = target.content
        
    return cell
}
  • numberOfRowsInSection : 테이블 뷰의 가로 행 수
  • cellForRowAt : 테이블 뷰의 가로 행 내용

근데 여기서 이런 오류가 발생했다.
텍스트가 자꾸 스위치가 있는데도 위로 써지는거다.... 캡쳐 사진에도 보이겠지만.... 한시간의 사투,,,,

근데 진짜 바보같은게.. 해결하고 나니까 허탈한데...
내가 첨부터 커스텀셀을 만들겠다고 했잖아???? 그러면 저 텍스트는 textLabel이 아니라 memoLabel로 설정했어야 하는데 기본값으로 설정해논 탓에........ 진짜 진짜 바보였음.. 역대급..ㅠ

하지만 해결했다는 기쁨이 더 크니까!!!

// 이거 절대절대 아니고!!!!
// cell.textLabel?.text = target.content

cell.memoLabel?.text = target.content


Alert 구현 화면


UITableViewCell

오늘 테이블 뷰 셀 때문에 2~3시간 에러와의 전쟁이였다.
이 클래스는 단일 행의 시각적 표현으로 크게 아래 4가지의 역할을 수행한다.

  • 선택 또는 강조 색상 적용
  • 세부 정보 또는 공개 컨트롤과 같은 표준 액세서리 보기 추가
  • 셀을 편집 가능한 상태로 만들기
  • 표에 시각적 계층 구조를 만들기 위해 셀 내용 들여쓰기

일단 나는 Xcode에서 제공하는 스타일을 적용하지 않고 Custom으로 작업했다. 왜냐면 나는 스위치를 넣고 싶기 때문에..^_^

얘도 별도의 클래스(TableViewCell)를 만들고 해당 클래스에서 셀에 관한 설정들을 작업했다.
어제 포스팅과 같이 메인 스토리보드 셀 안에 있는 라벨과 스위치를 TableViewCell 클래스에 연결해서 IBOutlet과 IBAction을 추가해준다.

기본적으로 클래스를 추가하면 이렇게 코드가 나오는데 이제 스위치를 눌렀을 때(off) 라벨에 취소선을 나타내고, 다시 스위치를 on 상태로 바꾸면 취소선이 사라지게 만들거다.

class TableViewCell: UITableViewCell {
    
    @IBOutlet weak var memoLabel: UILabel!
    @IBOutlet weak var memoSwitch: UISwitch!
    @IBAction func memoSwitch(_ sender: UISwitch) {
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
    
}

해당 기능을 구현하려고 서치를 하는데 strikeThrough 라는 함수를 만들어서 적용하면 된다는 블로그 간증들이 우수수 쏟아져나오더라.
그래서 일단 함수먼저 만들어놓고 본 클래스에서 적용하려고 했다.

아래 코드는 String 클래스를 확장(extension)하여, 문자열에 취소선을 추가하는 기능을 제공하는 함수이다.

extension String {
    func strikeThrough() -> NSAttributedString {
        let attributeString = NSMutableAttributedString(string: self)
        attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0, attributeString.length))
        return attributeString
    }
}
  • extension String { ... } : String 클래스 확장, 기존 String 클래스에 새로운 메서드 추가 가능
  • func strikeThrough() -> NSAttributedString { ... } : String 타입의 문자열에 적용, 취소선이 적용된 NSAttributedString 객체 반환
  • let attributeString = NSMutableAttributedString(string: self) : 원본 문자열(self)을 기반으로 새로운 NSMutableAttributedString 인스턴스를 생성
  • NSMutableAttributedString : 문자열에 특정 속성을 적용할 수 있는 가변 문자열 클래스입니다.
  • attributeString.addAttribute(...): NSMutableAttributedString에 특정 속성 추가(NSUnderlineStyle.single 값으로 NSUnderlineStyle 속성(취소선)을 설정하고, 전체 문자열 범위(NSMakeRange(0, * attributeString.length))에 적용)
  • return attributeString : 새로운 속성이 적용된 NSMutableAttributedString 객체를 반환

처음에는 스위치의 상태를 확인하고 true 이면 놔두고 false 이면 취소선을 만들고 attributedText를 nil값으로 작성했는데 스위치를 끄면 작성된 메모가 자꾸 사라지는 문제가 발생했다.
나는 취소선만 없애고 싶어서 해당 코드를 작성했는데, 블로그들을 다 뒤져봐도 textLabel.attributedText = nil 하면 다 된대.. 근데 왜 안 안돼..?

그래서 attributedText에 대해서 찾아 봤다.

  • attributedText
    • UILabel의 텍스트를 NSAttributedString으로 표현하는 속성
    • NSAttributedString은 텍스트에 다양한 속성을 적용할 수 있는 클래스로, 폰트, 색상, 스타일 등을 텍스트에 적용할 수 있음

그렇다. 나는 그냥 바보같이 텍스트까지 nil 로 처리한거였다..
그럼 기존에 적혀있는 메모를 다른 변수로 지정해서 갖고 있다가 취소선 해제할 때 attributedText를 nil 값으로 주고 변수를 다시 할당하면 된다.

    // 이전 텍스트를 저장할 변수 추가
    private var previousText: String?

    // 스위치 상태를 저장하는 변수
    var isSwitchOn = true

    @IBAction func memoSwitch(_ sender: UISwitch) {
        isSwitchOn = sender.isOn    // 스위치에 상태에 따라
        updateLabelStrikeThrough()  // 취소선 함수 실행
    }

    func updateLabelStrikeThrough() {
        if isSwitchOn {
            // 스위치가 on인 경우 취소선을 없애고 이전 텍스트 복원
            memoLabel.attributedText = nil
            memoLabel.text = previousText
        } else {
            // 스위치가 off인 경우 취소선을 추가하고 이전 텍스트 저장
            previousText = memoLabel.text
            memoLabel.attributedText = memoLabel.text?.strikeThrough()
        }
    }

오케! 오늘은 여기까지 완료했고 메모 추가, 메모 완료 처리(취소선)한 화면은 아래처럼 보인다.


최종 구현 화면


>꺼진 코드도 다시보자.. 제발 지정한 이름으로 작성했는지 확인하고 또 확인하자... 주석도 잘 달아놓자... 내일은 메모 눌렀을 때 상세페이지로 이동(지금은 팝업형태로 나오게 만들어 놓음)해서 수정 및 저장 기능*을 1차적으로 구현할 예정이다. 그리고 원활하게 잘 되면 삭제까지 !! **해당 기능은 prepare를 이용해서 작업할거임!!!! 이번주안에 완료한 일 연동도 가능할 지 모르겠지만 한번 해보자!!!!
profile
하지만 나는 끝까지 살아남을 거야!

0개의 댓글