감자주먹밥

노래방Book 앱 정리 - UI 변경 (TextField+UIDatePicker, TableView Infinite Scroll) 본문

IOS/UIKit

노래방Book 앱 정리 - UI 변경 (TextField+UIDatePicker, TableView Infinite Scroll)

JustHm 2024. 5. 5. 15:31
728x90

이번에는 간단하게 변경한 UI 2가지를 정리하려고 한다.


TableView 무한 스크롤의 경우 구현 한 적이 있었지만, 최신곡, 인기곡 등 그렇게 버거울 정도의 데이터가 한번에 들어오는게 아니여서 그냥 한번에 데이터를 불러와 보여줬었지만
노래 검색의 경우 "사랑" 단어를 포함한 노래만 4만곡 이상을 넘어가는 것으로 기억한다. 이것 때문이라도 페이징으로 데이터를 불러와야 할 거 같아 수정했다.

TextField+UIDatePicker는 최신곡 조회 기능에 날짜를 선택하기위해 사용했었는데, DatePicker에 연, 월 만 선택하는 걸 원했지만 당시에 왜 속성이 없지? 하면서 그냥 Date 전체를 선택할 수 있게 했던게 아쉬워서 수정하게 됐다.
수정을 위해 좀 알아보던 중 datePickerMode에 yearAndMonth 속성이 나온 것을 알게 됐지만 17.4 newer...
앱 미니멈 타겟은 14.0 이고 미니멈 타겟을 최신으로 올리기엔 과거에 했던 방식도 알아야겠다 싶어 그대로 두고 수정을 했다.


1. TableView 무한 스크롤

tableView.prefetchDataSource = presenter

MVP 패턴이어서 Delegate를 Presenter 클래스로 분리해서 구현해놨다.
PrefetchingDataSource는 화면에 보여지기 전 셀에서 처리해야 하는 연산을 할 수 있다.

prefetchRowsAt, cancelPrefetchingForRowsAt 두개를 많이 사용한다고 한다.

extension SearchResultPresenter: UITableViewDataSourcePrefetching {
    // 곧 보여질 셀들을 미리 불러오는 역할
    func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        guard currentPage != 1 else { return }
        
        indexPaths.forEach { // 1 page당 25 item
            if (($0.row + 1) / 20 + 1) == currentPage {
                self.searchSongs(brand: currentBrand, currentPage: currentPage)
            }
        }
    }
}

구현부에서는 보여질 예정인 셀의 IndexPath를 이용해 페이지의 값과 동일해지면 다음 페이지의 데이터를 가져오는 방식을 사용했다.
한 페이지 당 20개를 보여주며 currentPage 변수는 데이터를 가져올 때 마다 (searchSongs를 호출 한 후) 다음 페이지 번호를 저장하고 있다

첫 페이지를 기준으로 예시를 들어보면 row가 0~19까지 나올것이고 이를 limit 갯수인 20으로 나누면 현재 페이지의 값이 나온다.
그리고 거기서 + 1 을 추가로 해주면 다음 페이지와 값이 동일해 지기 때문에 호출을 할 수 있게 되는 것.

2. TextField + UIDatePicker

처음엔 UIDatePicker만 넣고 클릭하면 변경할 수 있는 화면이 나오게만 했었는데
다른 서비스에서처럼 TextField를 선택하고 UIDatePicker로 변경하는 것을 구현하는 것을 목표로 잡았다.

ViewController에 TextField, UIDatePicker, UIToolBar를 사용해 1차 수정을 했지만, 코드가 되게 많이 쌓이는 모습이 보기 좋지 않아 다른 방법을 찾던 중 TextField에 Extension을 만들어 처리를 하는 방식을 보고 그 코드를 이해해보고 적용해보자.

참고한 블로그가 있는데 클래스를 따로 분리해서 만든 Extension이라 파라미터도 좀 다르고 기능에 맞게 하느라 조금씩 다르다

전체 코드

더보기
extension UITextField {
    func datePickerMonthAndYear<T>(target: T,
                       doneAction: Selector,
                       cancelAction: Selector,
                       screenWidth: CGFloat) {
        // Create Toolbar Button
        func buttonItem(withSystemItemStyle style: UIBarButtonItem.SystemItem) -> UIBarButtonItem {
            let buttonTarget = style == .flexibleSpace ? nil : target
            let action: Selector? = {
                switch style {
                case .cancel:
                    return cancelAction
                case .done:
                    return doneAction
                default:
                    return nil
                }
            }()
            
            let barButtonItem = UIBarButtonItem(barButtonSystemItem: style,
                                                target: buttonTarget,
                                                action: action)
            
            return barButtonItem
        }
        // Setting up the UIDatePicker and the UIToolBar
        let datePicker = UIDatePicker(frame: CGRect(x: 0,
                                                    y: 0,
                                                    width: screenWidth,
                                                    height: 216))
        
        datePicker.locale = Locale(identifier: "ko_KR")
        datePicker.preferredDatePickerStyle = .wheels
        datePicker.maximumDate = Date()
        datePicker.datePickerMode = .init(rawValue: 4269) ?? .date /*왜 되는거지..?*/
        self.inputView = datePicker
        
        let toolBar = UIToolbar(frame: CGRect(x: 0,
                                              y: 0,
                                              width: screenWidth,
                                              height: 44))
        toolBar.setItems([buttonItem(withSystemItemStyle: .cancel),
                          buttonItem(withSystemItemStyle: .flexibleSpace),
                          buttonItem(withSystemItemStyle: .done)],
                         animated: true)
        self.inputAccessoryView = toolBar
    }
}

a. TextField에 Extension 만들기

func datePickerMonthAndYear<T>(target: T,
                   doneAction: Selector,
                   cancelAction: Selector,
                   screenWidth: CGFloat) {
}

먼저 Extension에 datePickerMonthAndYear라는 Generic 함수를 만들어 둔다.
Toolbar에 들어갈 액션 함수와 화면 크기를 사용할 예정이다.

b. ToolBar Button Item 만들기

func buttonItem(withSystemItemStyle style: UIBarButtonItem.SystemItem) -> UIBarButtonItem {
    let buttonTarget = style == .flexibleSpace ? nil : target
    let action: Selector? = {
        switch style {
        case .cancel:
            return cancelAction
        case .done:
            return doneAction
        default:
            return nil
        }
    }()
    
    let barButtonItem = UIBarButtonItem(barButtonSystemItem: style,
                                        target: buttonTarget,
                                        action: action)
    
    return barButtonItem
}

먼저 Toolbar의 버튼을 정의하는 함수를 먼저 정의한다. 나중에 ToolBar를 생성할 때 사용한다.

c. UIDatePicker, UIToolBar 생성 

let datePicker = UIDatePicker(frame: CGRect(x: 0,
                                            y: 0,
                                            width: screenWidth,
                                            height: 216))
datePicker.locale = Locale(identifier: "ko_KR")
datePicker.preferredDatePickerStyle = .wheels
datePicker.maximumDate = Date()
datePicker.datePickerMode = .init(rawValue: 4269) ?? .date /*왜 되는거지..?*/
self.inputView = datePicker

UIDatePicker를 생성하고 TextField의 inputView에 넣어준다. 이렇게 하면 키보드에 DatePicker를 사용할 수 있게 나온다.
글 초반에도 얘기했듯 datePickerMode에서 yearAndMonth 속성을 사용하면 되지만, 17.4 newer이라.. 
검색해보니 .init(rawValue: 4296) 을 쓰면 동일하게 사용할 수 있다고 했다. 

*보통은 그냥 PickerMode도 파라미터로 받으면 되겠지만, .init(rawValue:) 값을 전달하면 Default Value로 설정해도 계속 .date로 설정이 되는 바람에 아예 내부에서 바로 주입해줬다.

알고보니 4296은 그냥 yearAndMonth 속성의 상수값일 뿐... 정석인 방법이 아니라 그냥 우회방법인 것 같다.
그럼 결국 좋은 방법은 아니라는 건데..
위 고민을 예전에 하던 사람이 만든 MonthYearWheelPicker 라이브러리가 있는데 이걸 쓴다기 보다 코드를 참고하면 좋을 거 같아 가져와봤다.
https://github.com/bendodson/MonthYearWheelPicker/blob/master/Sources/MonthYearWheelPicker/MonthYearWheelPicker.swift

 

MonthYearWheelPicker/Sources/MonthYearWheelPicker/MonthYearWheelPicker.swift at master · bendodson/MonthYearWheelPicker

A UIPickerView subclass that allows you to quickly add a picker for just month and year; in most cases it can be used as a drop-in replacement for UIDatePicker. - bendodson/MonthYearWheelPicker

github.com

이런식으로 확장 말고도 아예 커스텀 해서 사용하는 방식도 좀 고려해야겠다. SegmentedControl도 그런식으로 검색해가며 만든 기억이 있는데 안해본지 오래되어서 잊어버린 거 같다.

let toolBar = UIToolbar(frame: CGRect(x: 0,
                                      y: 0,
                                      width: screenWidth,
                                      height: 44))
toolBar.setItems([buttonItem(withSystemItemStyle: .cancel),
                  buttonItem(withSystemItemStyle: .flexibleSpace),
                  buttonItem(withSystemItemStyle: .done)],
                 animated: true)
self.inputAccessoryView = toolBar

마지막으로 UIToolbar를 생성해 들어갈 Item들을 배열로 넣어주고 TextField에 inputAccessoryView에 넣으면 된다.
flexibleSpace의 경우 키보드를 열면 보이는 cancel, done 버튼을 양 옆에 배치할때 가운데를 빈 공간을 두기 위함이다.

d. 사용

private lazy var dateField: UITextField = {
    let field = UITextField()
    
    field.datePickerMonthAndYear(target: self,
                                 doneAction: #selector(doneAction),
                                 cancelAction: #selector(cancelAction),
                                 screenWidth: UIScreen.main.bounds.width)
    field.text = Date().dateToString(format: "yyyy년 MM월")
    field.borderStyle = .roundedRect
    return field
}()

사용은 간단하다.


이 정도로 이번 작업 정리를 끝마쳤다.

https://programmingwithswift.com/add-uidatepicker-in-a-uitextfield-with-swift/

 

Add UIDatePicker in a UITextField with Swift

In this tutorial I will show you how to add a UIDatePicker to a UITextField. Using a date picker can be a common input in forms when asking a user to sign up or you might be making a timer application and the list goes on. In some cases we

programmingwithswift.com

https://apps.apple.com/kr/app/노래방book/id1672848960

 

‎노래방Book

‎[주요 기능] - 최신곡 매일 확인 가능 - 인기차트 - 노래 저장기능 (애창곡) - 노래 이름 및 가수 검색 기능 - 유튜브에 올라와 있는 MR검색 [검색 가능한 노래방] - TJ, 금영

apps.apple.com

 

728x90
Comments