22 Apr 2022
|
iOS
개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
CoreMotion
걸음수 데이터를 가져오기 위한 방법은 두가지가 있습니다.
- Coremotion
- HealthKit
그치만 걸음수 데이터를 무엇을 위해, 어떻게 가져올지에 따라서 둘 중 한가지 방법을 선택해야 합니다.
우선 Coremotion은 실시간 걸음 데이터를 제공합니다.
일반적인 만보기 어플을 구현하기 위해서는 보통 coremotion을 사용합니다.
반면 Healthkit은 애플에서 제공해주는 건강앱에 표시되는 모든 건강데이터들을 그대로 가져옵니다.
즉, 실시간으로 걸음수 데이터를 수집할 목적이라면 coremotion을 사용하고 굳이 실시간 데이터가 필요하지않다면 healthkit을 사용해도 괜찮습니다.
이번에는 실시간으로 걸음수 데이터를 가져오는 coremotion 사용 방법에 대해서 설명해보겠습니다.
Info.plist
우선 걸음수 데이터를 가져오기 위해 유저에게 접근권한요청을 해야합니다.
plist에 다음과 같은 설정을 추가해줍니다.
NSMotionUsageDescription = 걸음수 데이터 측정을 위해 데이터 접근 권한이 필요합니다.
앞서 HealthKit 때도 말씀드렸듯이 이때 접근권한 메시지는 꼭 디테일하게 잘 적어줍시다! 추후 리젝사유가 됩니다.
code
import UIKit
import CoreMotion
class HomeViewController: UIViewController {
let activityManager = CMMotionActivityManager()
let pedoMeter = CMPedometer()
var stepData = 0
override func viewDidLoad() {
super.viewDidLoad()
updateStep()
}
func updateStep() {
if CMPedometer.isStepCountingAvailable() {
self.pedoMeter.queryPedometerData(from: date.startOfDay(), to: date.endOfDay()) { (date, error) in
if error == nil {
if let response = date {
DispatchQueue.main.async {
self.stepData = Int(truncating: response.numberOfSteps)
}
}
}
}
}
}
}
20 Apr 2022
|
iOS
개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
HealthKit
걸음수 데이터를 가져오기 위한 방법은 두가지가 있습니다.
- Coremotion
- HealthKit
그치만 걸음수 데이터를 무엇을 위해, 어떻게 가져올지에 따라서 둘 중 한가지 방법을 선택해야 합니다.
우선 Coremotion은 실시간 걸음 데이터를 제공합니다.
일반적인 만보기 어플을 구현하기 위해서는 보통 coremotion을 사용합니다.
반면 Healthkit은 애플에서 제공해주는 건강앱에 표시되는 모든 건강데이터들을 그대로 가져옵니다.
즉, 실시간으로 걸음수 데이터를 수집할 목적이라면 coremotion을 사용하고 굳이 실시간 데이터가 필요하지않다면 healthkit을 사용해도 괜찮습니다.
이번에는 실시간으로 걸음수 데이터를 가져오는 HealthKit 사용 방법에 대해서 설명해보겠습니다.
Info.plist
우선 걸음수 데이터를 가져오기 위해 유저에게 접근권한요청을 해야합니다.
plist에 다음과 같은 설정을 추가해줍니다.
NSHealthShareUsageDescription = 걸음수 데이터 측정을 위해 데이터 접근 권한이 필요합니다.
NSHealthUpdateUsageDescription = 걸음수 데이터 측정을 위해 데이터 접근 권한이 필요합니다.
이때 저 메시지.. 꼭 주의해서 작성하세요!
저는 나중에 수정해야지.. 하고 당장 개발할때 대충 작성하곤 까먹고.. 그대로 앱 심사를 올렸더니 문구 수정하라는 코멘트와 함께 리젝을 당했거든요..ㅠ흑
첨부터.. 조심조심 할수있는건 하고 지나가는게 좋겠구나! 라는 교휸을 얻었습니다 ㅎ
code
import UIKit
import HealthKit
class StepModel: NSObject {
var step: Double = 0.0
var date: Date = Date()
override init() {
super.init()
}
convenience init(step: Double, date: Date) {
self.init()
self.step = step
self.date = date
}
}
class HomeViewController: UIViewController {
let healthStore = HKHealthStore()
let typeToShare = HKObjectType.quantityType(forIdentifier: .stepCount)
let typeToRead = HKObjectType.quantityType(forIdentifier: .stepCount)
var stepDataList: [String] = []
var stepList: [StepModel] = [StepModel]()
override func viewDidLoad() {
super.viewDidLoad()
configure()
retrieveData()
}
func configure() {
print("configure")
if !HKHealthStore.isHealthDataAvailable() {
setHealthData()
} else {
requestAuthorization()
}
}
// 권한 요청
func requestAuthorization() {
print("requestAuthorization")
self.healthStore.requestAuthorization(toShare: Set([typeToShare!]), read: Set([typeToRead!])) { success, error in
if error != nil {
print(error?.localizedDescription as Any)
} else {
if success {
print("권한 승인 완료")
} else {
print("권한 승인 실패")
}
}
}
}
func setHealthData() {
print("retrieveData")
let calender = Calendar.current
var interval = DateComponents()
interval.day = 1
var anchorComponents = calender.dateComponents([.day, .month, .year], from: NSDate() as Date)
anchorComponents.hour = 0
let anchorDate = calender.date(from: anchorComponents)
let stepQuery = HKStatisticsCollectionQuery(quantityType: HKObjectType.quantityType(forIdentifier: .stepCount)!, quantitySamplePredicate: nil, options: .cumulativeSum, anchorDate: anchorDate!, intervalComponents: interval as DateComponents)
stepQuery.initialResultsHandler = { query, results, error in
let endDate = NSDate()
// 오늘로부터 30일간의 걸음수 데이터를 가져오도록 진행
let startDate = calender.date(byAdding: .day, value: -30, to: endDate as Date, wrappingComponents: false)
if let myResults = results {
myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
if let quantity = statistics.sumQuantity() {
let date = statistics.startDate
let steps = quantity.doubleValue(for: HKUnit.count())
self.stepDataList.append("\(date): \(steps)")
let model = StepModel(step: steps, date: date)
self.stepList.append(model)
DispatchQueue.main.async {
}
}
}
}
}
healthStore.execute(stepQuery)
}
func saveStepCount(stepValue: Int, date: Date, completion: @escaping (Error?) -> Void) {
guard let stepCountType = HKQuantityType.quantityType(forIdentifier: .stepCount) else { return }
let stepCountUnit: HKUnit = HKUnit.count()
let stepCountQuantity = HKQuantity(unit: stepCountUnit, doubleValue: Double(stepValue))
let stepCountSample = HKQuantitySample(type: stepCountType, quantity: stepCountQuantity, start: date, end: date)
self.healthStore.save(stepCountSample) { (success, error) in
if let error = error {
completion(error)
print("Error Saving Steps count Sample: \(error.localizedDescription)")
} else {
completion(nil)
print("Successfully saves step count sample")
}
}
}
}
15 Apr 2022
|
iOS
개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
UISeachBar 초성검색 기능 추가해보기
검색바에 좀더 다채로움을 주고싶어서.. 그게 뭐가 있을까 고민해보니..
초성검색 기능이 있으면 괜찮곘다 싶어졌습니다.
let hangeul = ["ㄱ","ㄲ","ㄴ","ㄷ","ㄸ","ㄹ","ㅁ","ㅂ","ㅃ","ㅅ","ㅆ","ㅇ","ㅈ","ㅉ","ㅊ","ㅋ","ㅌ","ㅍ","ㅎ"]
func chosungCheck(word: String) -> String {
var result = ""
for char in word {
// unicodeScalars: 유니코드 스칼라 값의 모음으로 표현되는 문자열 값
let octal = char.unicodeScalars[char.unicodeScalars.startIndex].value
// ~=: 왼쪽에서 정의한 범위 값 안에 오른쪽의 값이 속하면 true, 아니면 false 반환
if 44032...55203 ~= octal {
let index = (octal - 0xac00) / 28 / 21
result = result + hangeul[Int(index)]
}
}
return result
}
func isChosung(word: String) -> Bool {
var isChosung = false
for char in word {
if 0 < hangeul.filter({ $0.contains(char)}).count {
isChosung = true
} else {
isChosung = false
break
}
}
return isChosung
}
그럼 이 코드를 서치바에서 어떻게 적용시킬까요?
var arr = ["zehye", "hi", "hello", "nice", "to", "meet", "you"]
var filterredArr: [String] = []
extension SearchViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// 텍스트의 공백 제거
let text = searchText.trimmingCharacters(in: .whitespaces)
// 서치바에 적힌 공백이 제거된 글자의 초성을 체크
let isChosungCheck = isChosung(word: text)
// filterText라는 상수를 받아 arr를 필터링 합니다.
// 서치바에서 받은 text에 arr의 초성이 포함되어있는지 여부를 따집니다.
let filterText = arr.filter({
if isChosungCheck {
return ($0.contains(text) || chosungCheck(word: $0).contains(text))
} else {
return $0.contains(text)
}
})
// 포함되는 filterText를 filterredArr에 담아주고
self.filterredArr = filterText
// 테이블뷰 리로드를 합니다.
self.tableView.reloadData()
}
// 검색버튼을 눌렀을때도 마찬가지의 로직을 진행합니다.
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = self.searchBar.text?.trimmingCharacters(in: .whitespaces) else { return }
let isChosungCheck = isChosung(word: text)
let filterText = list.filter({
if isChosungCheck {
return ($0.contains(text) || chosungCheck(word: $0).contains(text))
} else {
return $0.contains(text)
}
})
self.filterredArr = filterText
self.isFiltering = !(self.searchBar.text?.count == 0)
self.tableView.reloadData()
}
}
10 Apr 2022
|
iOS
개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
SearchController 두번째, 검색바 데이터 filtering 처리 해보기
사실 우리가 검색 컨트롤러를 사용하는 이유는 아래와 같죠.
- 내가 검색한 내용이 실제 검색이 되어야한다.
제일 중요한 부분입니다.
그러면 이제 그 제일 중요한 부분을 다뤄보도록 하겠습니다.
우선…
우선 기본적으로 제가 한 셋팅을 보여드리겠습니다.
class SearchViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
var arr = ["zehye", "hi", "hello", "nice", "to", "meet", "you"]
override func viewDidLoad() {
super.viewDidLoad()
self.initUI()
self.setSearchControllerUI()
}
func initUI() {
self.tableView.delegate = self
self.tableView.dataSource = self
}
func setSearchControllerUI() {
let searchController = UISearchController(searchResultsController: nil)
// set placeholder
let placeholder = "검색창입니다"
searchController.searchBar.placeholder = placeholder
// searchController가 검색하는 동안 네비게이션에 가려지지 않도록
searchController.hidesNavigationBarDuringPresentation = false
// searchBar cancel button text change (cancel을 취소로 텍스트 변경)
searchController.searchBar.setValue("취소", forKey: "cancelButtonText")
// placeholder릐 글꼴과 글자 크기 변경
let attributedString = NSMutableAttributedString(string: placeholder, attributes: [
NSAttributedString.Key.font: UIFont(name: "Spoqa Han Sans Neo", size: 16) as Any,
NSAttributedString.Key.foregroundColor: UIColor(white: 255/255, alpha: 0.3)
])
searchController.searchBar.searchTextField.attributedPlaceholder = attributedString
// searchBar의 textField의 배경색, 폰트, 글자크기 변경
searchController.searchBar.searchTextField.backgroundColor = .clear
searchController.searchBar.searchTextField.font = UIFont(name: "Spoqa Han Sans Neo", size: 16)
// clear button 설정
if let button = searchController.searchBar.searchTextField.value(forKey: "_clearButton") as? UIButton {
let templateImage = button.imageView?.image?.withRenderingMode(.alwaysTemplate)
button.setImage(templateImage, for: .normal)
button.tintColor = .white
}
// 서치 컨트롤러 왼쪽에 back button color 변경
searchController.searchBar.searchTextField.leftView?.tintColor = UIColor.white
// searchBar cancel button color
searchController.searchBar.tintColor = UIColor.white
// navigation title
self.navigationItem.searchController = searchController
self.navigationItem.title = "검색하기"
// searchBar back button color
self.navigationItem.searchController?.searchBar.barTintColor = UIColor.white
// navigation item 스크롤 될때 사라지지 않게
self.navigationItem.hidesSearchBarWhenScrolling = false
// navigation item back button > back text remove (back 문구 없애기)
self.navigationController?.navigationBar.topItem?.backButtonDisplayMode = .minimal
// navigation item back button 이미지 변경
self.navigationController?.navigationBar.backIndicatorImage = UIImage(named: "icBack")
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = UIImage(named: "icBack")
}
}
extension SearchViewController: UITableViewDelegate, UITableViewDataSource {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.arr.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SeachListCell") as! SearchListTableViewCell
cell.textLabel?.text = self.arr[indexPath.row]
return cell
}
}
- initUI()를 통해 테이블뷰 셋팅을 진행했고
- setSearchControllerUI()를 통해 서치 컨트롤러 셋팅을 진행했습니다.
- 나머지 tableview delegate, datasource 처리를 하였구요.
그럼 이제 중요한건 무엇일까요?
- 유저가 서치바에 무언가를 검색한다.
- 그러면 입력할때마다 해당되는 검색 내용이 아래 리로드 될 수 있도록 한다.
Filtering 진행
우선 서치바에 텍스트가 업데이트 될때 마다 불리는 메소드가 있어야 합니다.
searhController.searchResultsUpdater = self
위 코드를 setSearchControllerUI에 추가해 줍니다.
그리고 아래 코드도 같이 추가해 줍니다.
extension SearchController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
// code
}
}
위 코드는 우리가 서치바에 무언가를 검색할때마다 불려지는 함수입니다.
이 함수 안에서 우리가 서치바에 무언가 검색할때 무엇을 처리할지 적어주면 됩니다.
class SearchViewController: UIViewContoller {
...
var filterredArr: [String] = []
...
}
extension SearchController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
guard let text = searchController.searchBar.text?.lowercased() else { return }
self.filterredArr = self.arr.filter { $0.localizedCaseInsensitiveContains(text) }
dump(filterredArr)
}
}
이러면 로그에는 서치바가 업데이트 될때마다 반응을 하지만 실제 화면에서는 이루어지질 않죠?
그 이유는 아직 테이블 뷰에서 필터링된 데이터들에 대한 처리를 해주고 있지 않기 때문입니다.
class SearchViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
var arr = ["zehye", "hi", "hello", "nice", "to", "meet", "you"]
var filterredArr: [String] = []
var isFiltering: Bool {
let searchController = self.navigationItem.searchController
let isActive = searchController?.isActive ?? false
let isSearchBarHasText = searchController?.serarchBar.text?.isEmpty == false
return isActive && isSearchBarHasText
}
override func viewDidLoad() {
super.viewDidLoad()
self.initUI()
self.setSearchControllerUI()
}
func initUI() {
self.tableView.delegate = self
self.tableView.dataSource = self
}
func setSearchControllerUI() {
let searchController = UISearchController(searchResultsController: nil)
// set placeholder
let placeholder = "검색창입니다"
searchController.searchBar.placeholder = placeholder
// searchController가 검색하는 동안 네비게이션에 가려지지 않도록
searchController.hidesNavigationBarDuringPresentation = false
// searchBar cancel button text change (cancel을 취소로 텍스트 변경)
searchController.searchBar.setValue("취소", forKey: "cancelButtonText")
// placeholder릐 글꼴과 글자 크기 변경
let attributedString = NSMutableAttributedString(string: placeholder, attributes: [
NSAttributedString.Key.font: UIFont(name: "Spoqa Han Sans Neo", size: 16) as Any,
NSAttributedString.Key.foregroundColor: UIColor(white: 255/255, alpha: 0.3)
])
searchController.searchBar.searchTextField.attributedPlaceholder = attributedString
// searchBar의 textField의 배경색, 폰트, 글자크기 변경
searchController.searchBar.searchTextField.backgroundColor = .clear
searchController.searchBar.searchTextField.font = UIFont(name: "Spoqa Han Sans Neo", size: 16)
// clear button 설정
if let button = searchController.searchBar.searchTextField.value(forKey: "_clearButton") as? UIButton {
let templateImage = button.imageView?.image?.withRenderingMode(.alwaysTemplate)
button.setImage(templateImage, for: .normal)
button.tintColor = .white
}
// 서치 컨트롤러 왼쪽에 back button color 변경
searchController.searchBar.searchTextField.leftView?.tintColor = UIColor.white
// searchBar cancel button color
searchController.searchBar.tintColor = UIColor.white
// navigation title
self.navigationItem.searchController = searchController
self.navigationItem.title = "검색하기"
// searchBar back button color
self.navigationItem.searchController?.searchBar.barTintColor = UIColor.white
// navigation item 스크롤 될때 사라지지 않게
self.navigationItem.hidesSearchBarWhenScrolling = false
// navigation item back button > back text remove (back 문구 없애기)
self.navigationController?.navigationBar.topItem?.backButtonDisplayMode = .minimal
// navigation item back button 이미지 변경
self.navigationController?.navigationBar.backIndicatorImage = UIImage(named: "icBack")
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = UIImage(named: "icBack")
searhController.searchResultsUpdater = self
}
}
extension SearchViewController: UITableViewDelegate, UITableViewDataSource {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.isFilterting ? self.filterredArr.count : self.arr.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SeachListCell") as! SearchListTableViewCell
if self.isFiltering {
cell.textLabel?.text = self.filterredArr[indexPath.row]
} else {
cell.textLabel?.text = self.arr[indexPath.row]
}
return cell
}
}
extension SearchController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
guard let text = searchController.searchBar.text?.lowercased() else { return }
self.filterredArr = self.arr.filter { $0.localizedCaseInsensitiveContains(text) }
self.tableView.reloadData()
}
}
08 Apr 2022
|
iOS
개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
SearchController 첫번째, 기본 셋팅해보기
프로젝트에서 검색창을 구현하기 위해 searchController를 사용하게 되었습니다.
그치만 결론은 앞으로는 서치바 자체만을 사용하게 될 것같다는 말씀…!
서치 컨트롤러는 사용해보니 화면상에서 서치 컨트롤러 자체가 제약을 두는 것이 많았습니다.
화면에 컨트롤러 하나가 더 겹쳐지게 됨으로써 커스텀을 하는데에도 찾아봐야할 것들이 많았구요.
검색을 위한 행위를 만들것이라면 그저 searchBar를 올려두고 그 안에서 해결하는것이 좀더 좋은 방법이 될 것 같다고 생각합니다..
좀더.. 간단해요! 그게…ㅎㅎ
그럼에도 서치 컨트롤러를 쓴다면! 아래와 같이 진행해보세요.
UISearchController
저는 서치 컨트롤러를 셋팅해주는 함수 하나를 만들어줄 예정이며, 이 함수를 viewDidLoad에서 불러서 사용할 것입니다.
func setSearchControllerUI() {
let searchController = UISearchController(searchResultsController: nil)
self.navigationItem.searchController = searchController
}
위와 같이 initialize를 해줍니다.
서치 컨트롤러 안에 서치바가 있기 때문에 서치바와 관련된 설정 코드들을 추가해줄수도 있습니다.
func setSearchControllerUI() {
let searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.placeholder = "검색창입니다"
self.navigationItem.searchController = searchController
}
이때 searchResultController는 무엇일까요?
우선 이 searchResultController에는 검색결과를 표시하고 싶은 뷰컨이 들어가면 됩니다.
예제 코드
기본적으로 우리가 서치 컨트롤러를 사용하면 알아야할 프로퍼티들입니다.
func setSearchControllerUI() {
let searchController = UISearchController(searchResultsController: nil)
// set placeholder
let placeholder = "검색창입니다"
searchController.searchBar.placeholder = placeholder
// searchController가 검색하는 동안 네비게이션에 가려지지 않도록
searchController.hidesNavigationBarDuringPresentation = false
// searchBar cancel button text change (cancel을 취소로 텍스트 변경)
searchController.searchBar.setValue("취소", forKey: "cancelButtonText")
// placeholder릐 글꼴과 글자 크기 변경
let attributedString = NSMutableAttributedString(string: placeholder, attributes: [
NSAttributedString.Key.font: UIFont(name: "Spoqa Han Sans Neo", size: 16) as Any,
NSAttributedString.Key.foregroundColor: UIColor(white: 255/255, alpha: 0.3)
])
searchController.searchBar.searchTextField.attributedPlaceholder = attributedString
// searchBar의 textField의 배경색, 폰트, 글자크기 변경
searchController.searchBar.searchTextField.backgroundColor = .clear
searchController.searchBar.searchTextField.font = UIFont(name: "Spoqa Han Sans Neo", size: 16)
// clear button 설정
if let button = searchController.searchBar.searchTextField.value(forKey: "_clearButton") as? UIButton {
let templateImage = button.imageView?.image?.withRenderingMode(.alwaysTemplate)
button.setImage(templateImage, for: .normal)
button.tintColor = .white
}
// 서치 컨트롤러 왼쪽에 back button color 변경
searchController.searchBar.searchTextField.leftView?.tintColor = UIColor.white
// searchBar cancel button color
searchController.searchBar.tintColor = UIColor.white
// navigation title
self.navigationItem.searchController = searchController
self.navigationItem.title = "검색하기"
// searchBar back button color
self.navigationItem.searchController?.searchBar.barTintColor = UIColor.white
// navigation item 스크롤 될때 사라지지 않게
self.navigationItem.hidesSearchBarWhenScrolling = false
// navigation item back button > back text remove (back 문구 없애기)
self.navigationController?.navigationBar.topItem?.backButtonDisplayMode = .minimal
// navigation item back button 이미지 변경
self.navigationController?.navigationBar.backIndicatorImage = UIImage(named: "icBack")
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = UIImage(named: "icBack")
}