swiftでiPhone標準の時計アプリを作ろう 完成コード 世界時計

2019年9月17日火曜日

swift 時計 世界時計

t f B! P L

Xcode 10.3
ios 12.4
swift5

完成品


UI

 

コード

WorldClockVC

  1. import UIKit
  2.  
  3. class WorldClockVC: UIViewController ,UITableViewDelegate,UITableViewDataSource{
  4. var selectTimezones = [String]()
  5. var userDefaults = UserDefaults.standard
  6. var timer = Timer()
  7.  
  8. @IBOutlet weak var tableView: UITableView!
  9. override func viewDidLoad() {
  10. super.viewDidLoad()
  11. loadTimeZones()
  12. tableView.register(UINib(nibName: "WorldClockCell", bundle: nil), forCellReuseIdentifier: "WorldClockCell")
  13. self.navigationItem.setLeftBarButton(self.editButtonItem, animated: true)
  14. }
  15. func loadTimeZones(){
  16. if let getTimeZones = userDefaults.object(forKey: "TimeZones") as? [String]{
  17. selectTimezones = getTimeZones
  18. }
  19. }
  20. func saveTimeZones(){
  21. userDefaults.set(selectTimezones, forKey: "TimeZones")
  22. userDefaults.synchronize()
  23. }
  24. override func setEditing(_ editing: Bool, animated: Bool) {
  25. super.setEditing(editing, animated: animated)
  26. tableView.setEditing(editing, animated: animated)
  27. }
  28. func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
  29. return true
  30. }
  31. func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
  32. return true
  33. }
  34. func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
  35. let sourceCellItem = selectTimezones[sourceIndexPath.row]
  36. guard let indexPath = selectTimezones.firstIndex(of: sourceCellItem) else { return }
  37. selectTimezones.remove(at: indexPath)
  38. selectTimezones.insert(sourceCellItem, at: destinationIndexPath.row)
  39. saveTimeZones()
  40. }
  41. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  42. return selectTimezones.count
  43. }
  44. func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  45. return 60
  46. }
  47. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  48. let cell = tableView.dequeueReusableCell(withIdentifier: "WorldClockCell") as! WorldClockCell
  49. cell.setCity(name: selectTimezones[indexPath.row])
  50. return cell
  51. }
  52. func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
  53. if editingStyle == .delete {
  54. selectTimezones.remove(at: indexPath.row)
  55. tableView.deleteRows(at: [indexPath], with: .fade)
  56. saveTimeZones()
  57. }
  58. }
  59. func timeZone(city:String) -> String{
  60. let formatter = DateFormatter()
  61. formatter.dateFormat = "HH:mm"
  62. let timeZoneIdentifiers = TimeZone.knownTimeZoneIdentifiers
  63. for identifier in timeZoneIdentifiers {
  64. if identifier.contains(city){
  65. formatter.timeZone = TimeZone(identifier: identifier)
  66. }
  67. }
  68. return formatter.string(from: Date())
  69. }
  70. override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  71. guard let nvc = segue.destination as? UINavigationController else {return}
  72. guard let vc = nvc.topViewController as? WorldClockSelectVC else {return}
  73. vc.delegate = self
  74. }
  75. }
  76.  
  77. extension WorldClockVC:WorldClockSelectVCDelegte{
  78. func worldClockSelect(cancel: WorldClockSelectVC) {
  79. self.setEditing(false, animated: false)
  80. }
  81. func worldClockSelect(selectedWorldClock: WorldClockSelectVC, selected: String) {
  82. if !selectTimezones.contains(selected){
  83. selectTimezones.append(selected)
  84. }
  85. tableView.reloadData()
  86. saveTimeZones()
  87. }
  88. }

WorldClockCell

  1. import UIKit
  2.  
  3. class WorldClockCell: UITableViewCell {
  4.  
  5. @IBOutlet weak var timeLabel: UILabel!
  6. @IBOutlet weak var cityLabel: UILabel!
  7. @IBOutlet weak var hrsLabel: UILabel!
  8. var timer = Timer()
  9. var cityName:String!
  10.  
  11. override func awakeFromNib() {
  12. super.awakeFromNib()
  13. self.accessoryView = timeLabel
  14. }
  15. func setCity(name:String){
  16. cityLabel.text = name
  17. timeLabel.text = timeZone(city: name)
  18. hrsLabel.text = "Today,+0HRS"
  19. cityName = name
  20. timer = Timer.scheduledTimer(
  21. timeInterval: 1.0, // 時間間隔
  22. target: self, // タイマーの実際の処理の場所
  23. selector: #selector(WorldClockCell.tickTimer(_:)), // メソッド タイマーの実際の処理
  24. userInfo: nil,
  25. repeats: true)
  26. RunLoop.main.add(timer, forMode: RunLoop.Mode.common)
  27. }
  28. @objc func tickTimer(_ timer: Timer) {
  29. timeLabel.text = timeZone(city: cityName)
  30. }
  31. func timeZone(city:String) -> String{
  32. let formatter = DateFormatter()
  33. formatter.dateFormat = "HH:mm"
  34. formatter.timeStyle = .short
  35. formatter.locale = Locale(identifier: "ja_JP")
  36. let timeZoneIdentifiers = TimeZone.knownTimeZoneIdentifiers
  37. for identifier in timeZoneIdentifiers {
  38. if identifier.contains(city){
  39. formatter.timeZone = TimeZone(identifier: identifier)
  40. }
  41. }
  42. let timeDiff = formatter.timeZone.secondsFromGMT()
  43. hrsLabel.text = timeDiff.timeString()
  44. return formatter.string(from: Date())
  45. }
  46. }
  47.  
  48. extension Int {
  49. func timeString() -> String {
  50. let adjustSecondsForJapan : TimeInterval = 9 * 60 * 60
  51. let formatter = DateComponentsFormatter()
  52. formatter.allowedUnits = [.day,.hour, .minute]
  53. formatter.unitsStyle = .positional
  54. let formattedString = formatter.string(from: TimeInterval(self) - adjustSecondsForJapan) ?? "0"
  55. let df = DateFormatter()
  56. df.dateStyle = .short
  57. df.doesRelativeDateFormatting = true
  58. let day = df.string(from: Date(timeIntervalSinceNow: TimeInterval(self) - adjustSecondsForJapan))
  59. if formattedString == "0" {
  60. return day + ", +0HRS"
  61. } else {
  62. if formattedString.hasPrefix("-") {
  63. return day + ", \(formattedString)HRS"
  64. } else {
  65. return day + "+\(formattedString)HRS"
  66. }
  67. }
  68. }
  69. }

WorldClockSelectVC

  1. import UIKit
  2.  
  3. protocol WorldClockSelectVCDelegte {
  4. func worldClockSelect(selectedWorldClock:WorldClockSelectVC,selected:String)
  5. func worldClockSelect(cancel:WorldClockSelectVC)
  6. }
  7.  
  8. class WorldClockSelectVC: UIViewController {
  9.  
  10. @IBOutlet weak var tableView: UITableView!
  11. @IBOutlet weak var nav: UINavigationBar!
  12. var delegate:WorldClockSelectVCDelegte!
  13. private var searchController: UISearchController!
  14. var filteredTitles = [[String]]()
  15. var timeZoneIdentifiers = [String]()
  16. var allCities: [String] = []
  17. var sortedFirstLetters: [String] = []
  18. var sections: [[String]] = [[]]
  19. var searchString:String = ""
  20. override func viewDidLoad() {
  21. super.viewDidLoad()
  22. searchController = UISearchController(searchResultsController: nil)
  23. searchController.searchResultsUpdater = self
  24. searchController.obscuresBackgroundDuringPresentation = false
  25. searchController.dimsBackgroundDuringPresentation = false
  26. searchController.searchBar.sizeToFit()
  27. searchController.searchBar.showsCancelButton = true
  28. searchController.searchBar.delegate = self
  29. // UISearchControllerをUINavigationItemのsearchControllerプロパティにセットする。
  30. navigationItem.searchController = searchController
  31. // trueだとスクロールした時にSearchBarを隠す(デフォルトはtrue)
  32. // falseだとスクロール位置に関係なく常にSearchBarが表示される
  33. navigationItem.hidesSearchBarWhenScrolling = false
  34. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  35. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
  36. timeZoneIdentifiers = TimeZone.knownTimeZoneIdentifiers.sorted()
  37. for identifier in timeZoneIdentifiers {
  38. if let cityName = identifier.split(separator: "/").last {
  39. allCities.append("\(cityName)")
  40. }
  41. }
  42. let firstLetters = allCities.map { $0[$0.startIndex].uppercased() }
  43. let uniqueFirstLetters = Array(Set(firstLetters))
  44. sortedFirstLetters = uniqueFirstLetters.sorted()
  45. sections = sortedFirstLetters.map({firstLetter in return allCities.filter({ $0[$0.startIndex].uppercased() == firstLetter }).sorted(by: {$0 < $1})
  46. })
  47. filteredTitles = sections
  48. }
  49. @objc private func keyboardWillShow(notification: NSNotification) {
  50. if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
  51. tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
  52. }
  53. }
  54. @objc private func keyboardWillHide(notification: NSNotification) {
  55. tableView.contentInset = .zero
  56. }
  57. }
  58.  
  59. // MARK: - UISearchResultsUpdating
  60.  
  61. extension WorldClockSelectVC: UISearchResultsUpdating ,UISearchBarDelegate{
  62. func updateSearchResults(for searchController: UISearchController) {
  63. // SearchBarに入力したテキストを使って表示データをフィルタリングする。
  64. searchString = searchController.searchBar.text ?? ""
  65. if searchString.isEmpty {
  66. filteredTitles = sections
  67. } else {
  68. filteredTitles = sections.map({
  69. filter in return filter.filter({$0.contains(searchString)})
  70. }
  71. )
  72. }
  73. tableView.reloadData()
  74. }
  75. func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
  76. delegate.worldClockSelect(cancel: self)
  77. dismiss(animated: true, completion: nil)
  78. }
  79. }
  80.  
  81. // MARK: - UITableViewDataSource UITableViewDelegate
  82. extension WorldClockSelectVC: UITableViewDataSource,UITableViewDelegate {
  83. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  84. return filteredTitles[section].count
  85. }
  86. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  87. let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
  88. cell.textLabel?.text = filteredTitles[indexPath.section][indexPath.row]
  89. return cell
  90. }
  91. public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  92. return searchString != "" ? nil : sortedFirstLetters[section]
  93. }
  94. func numberOfSections(in tableView: UITableView) -> Int {
  95. return filteredTitles.count
  96. }
  97. public func sectionIndexTitles(for tableView: UITableView) -> [String]? {
  98. return searchString != "" ? nil : sortedFirstLetters
  99. }
  100. public func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
  101. return UILocalizedIndexedCollation.current().section(forSectionIndexTitle: index)
  102. }
  103. func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  104. searchController.searchBar.resignFirstResponder()
  105. searchController.dismiss(animated: false)
  106. delegate.worldClockSelect(selectedWorldClock: self, selected: filteredTitles[indexPath.section][indexPath.row])
  107. dismiss(animated: true, completion: nil)
  108. }
  109. }
githubはこちら

参考

UISearchControllerの使い方。主に結果画面からNavigationControllerでPush
swift – カスタムモデルクラスを使用して、tableView内のデータをセクションごとにアルファベット順にソートする方法
Replacement of date object with “today” and “yesterday” strings in iphone

QooQ