diff --git a/Classes/CityCountryTimeZone.swift b/Classes/CityCountryTimeZone.swift index 88f184c..bc2ffed 100644 --- a/Classes/CityCountryTimeZone.swift +++ b/Classes/CityCountryTimeZone.swift @@ -3,16 +3,24 @@ // TimeZonePicker // // Created by Gligor Kotushevski on 19/07/17. +// Edited by Malcolm Anderson on 7/25/20. // Copyright © 2017 Gligor Kotushevski. All rights reserved. // import Foundation -struct CityCountryTimeZone: Codable { +struct CityCountryTimeZone: Codable, Hashable, Identifiable { + var city = "" + var country = "" + var timeZoneName = "" - let city: String - let country: String - let timeZoneName: String + public var id: String { return city + country } + + init() { + self.city = "NULL" + self.country = "NULL" + self.timeZoneName = "NULL" + } init(city: String, country: String, timeZoneName: String) { self.city = city @@ -21,7 +29,7 @@ struct CityCountryTimeZone: Codable { } func contains(_ string: String) -> Bool { - return city.lowercased().contains(string.lowercased()) || country.lowercased().contains(string.lowercased()) + return self.string().lowercased().contains(string.lowercased()) } func string() -> String { diff --git a/Classes/SwiftUI/TimeZoneDataManager.swift b/Classes/SwiftUI/TimeZoneDataManager.swift new file mode 100644 index 0000000..856dc8b --- /dev/null +++ b/Classes/SwiftUI/TimeZoneDataManager.swift @@ -0,0 +1,53 @@ +// +// TimeZoneDataManager.swift +// TimeZonePicker +// +// Created by Malcolm Anderson on 7/25/20. +// Copyright © 2020 Malcolm Anderson. All rights reserved. +// + +import Foundation + +class TimeZoneDataManager { + static let sharedInstance = TimeZoneDataManager() + + var timeZones: [CityCountryTimeZone] = [] + + private init() { + self.loadTimeZoneData() + } + + func loadTimeZoneData() { + // copied from TimeZonePickerDataSource.swift + DispatchQueue.global(qos: .userInitiated).async { + do { + if let file = Bundle(for: TimeZoneDataManager.self).url(forResource: "CitiesAndTimeZones", withExtension: "json") { + let data = try Data(contentsOf: file) + do { + var timeZones = try JSONDecoder().decode([CityCountryTimeZone].self, from: data) + timeZones.sort() + DispatchQueue.main.async { + self.timeZones = timeZones + print("TimeZoneDataManager - setup is complete") + } + } catch { + // should never get here / invalid json + print("TimeZoneDataManager - invalid JSON") + } + } else { + // should never get here / file does not exist + print("TimeZoneDataManager - JSON file doesn't exist") + } + } catch { + // should never get here / unless Data or JSONSerialization throw an error + print("TimeZoneDataManager - Data or JSONSerialization threw error") + } + } + } + + func filter(_ str: String) -> [CityCountryTimeZone] { + return self.timeZones.filter { timeZone -> Bool in + timeZone.contains(str) + } + } +} diff --git a/Classes/SwiftUI/TimeZonePreviewSelectionView.swift b/Classes/SwiftUI/TimeZonePreviewSelectionView.swift new file mode 100644 index 0000000..9693f05 --- /dev/null +++ b/Classes/SwiftUI/TimeZonePreviewSelectionView.swift @@ -0,0 +1,26 @@ +// +// TimeZonePreviewSelectionView.swift +// TimeZonePicker +// +// Created by Malcolm Anderson on 7/25/20. +// Copyright © 2020 Malcolm Anderson. All rights reserved. +// + +import SwiftUI + +struct TimeZonePreviewSelectionView: View { + @Binding var cityItem: CityCountryTimeZone? + @State var selectorEnabled = false + var body: some View { + HStack { + Text(cityItem != nil ? cityItem!.string() : "None") + Spacer() + Button(action: {self.selectorEnabled = true}) { + Text("Select...") + } + }.sheet(isPresented: $selectorEnabled) { + TimeZoneSelectorView(searchContent: (self.cityItem != nil ? self.cityItem!.string() : ""), selectedTimeZone: self.$cityItem) + } + + } +} diff --git a/Classes/SwiftUI/TimeZoneSelectorView.swift b/Classes/SwiftUI/TimeZoneSelectorView.swift new file mode 100644 index 0000000..0831296 --- /dev/null +++ b/Classes/SwiftUI/TimeZoneSelectorView.swift @@ -0,0 +1,41 @@ +// +// TimeZoneSelectorView.swift +// TimeZonePicker +// +// Created by Malcolm Anderson on 7/25/20. +// Copyright © 2020 Malcolm Anderson. All rights reserved. +// + +import SwiftUI + +struct TimeZoneSelectorView: View { + @State var searchContent = "" + @Binding var selectedTimeZone: CityCountryTimeZone? + @Environment(\.presentationMode) var presentationMode + var body: some View { + VStack { + TextField("Search", text: self.$searchContent) + List(self.getFilteredList(), id: \.self, selection: self.$selectedTimeZone) { item in + Text(item.string()).tag(item) + + } + HStack { + Button(action: cancel) { Text("Cancel") } + Button(action: confirm) { Text("Select") } + } + } + .frame(minWidth: 200, minHeight: 200) + } + + func getFilteredList() -> [CityCountryTimeZone] { + return TimeZoneDataManager.sharedInstance.filter(self.searchContent) + } + + func cancel() { + self.presentationMode.wrappedValue.dismiss() + } + + func confirm() { + self.presentationMode.wrappedValue.dismiss() + } +} diff --git a/README.md b/README.md index d63643a..c9ea4cc 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Buy Me a Coffee at ko-fi.com -A TimeZonePicker UIViewController similar to the iOS Settings app. Search and select from a range of cities and countries to find your most suitable time zone. +A TimeZonePicker UIViewController and SwiftUI View similar to the iOS Settings app. Search and select from a range of cities and countries to find your most suitable time zone. ## Screenshots @@ -61,6 +61,31 @@ func timeZonePicker(_ timeZonePicker: TimeZonePickerViewController, didSelectTim Please check the `TimeZonePickerExample` project for the above usage example. If you have any questions do not hesitate to get in touch with me. +### SwiftUI +In addition to the UIKit-based view controllers, there is also a SwiftUI View available, called `TimeZonePreviewSelectionView`. + +![Preview view](Screenshots/SwiftUI_macOS_preview.png) +![Select view](Screenshots/SwiftUI_macOS_picker.png) + +To use TimeZonePicker in a SwiftUI app, include `TimeZonePreviewSelectionView()` in your `View`, and bind it to a `CityCountryTimeZone` variable, like so: +```swift +struct ContentView: View { + // variable storing the time zone data + @State var myTimeZone: CityCountryTimeZone = CityCountryTimeZone() + + var body: some View { + VStack { + // Creates the time zone preview picker + TimeZonePreviewSelectionView(cityItem: self.$myTimeZone) + + // Displays the selected time zone + Text("The selected time zone is \(self.myTimeZone.timeZoneName)") + } + } +} +``` +**Note:** So far, this has only been tested on macOS Catalina. It *should* work on iOS as well, but it is not guaranteed yet. + ## Requirements * iOS 8 or later. diff --git a/Screenshots/SwiftUI_macOS_picker.png b/Screenshots/SwiftUI_macOS_picker.png new file mode 100644 index 0000000..659e852 Binary files /dev/null and b/Screenshots/SwiftUI_macOS_picker.png differ diff --git a/Screenshots/SwiftUI_macOS_preview.png b/Screenshots/SwiftUI_macOS_preview.png new file mode 100644 index 0000000..e648aeb Binary files /dev/null and b/Screenshots/SwiftUI_macOS_preview.png differ diff --git a/TimeZonePicker.podspec b/TimeZonePicker.podspec index 23db4d6..d75472b 100644 --- a/TimeZonePicker.podspec +++ b/TimeZonePicker.podspec @@ -2,25 +2,26 @@ Pod::Spec.new do |s| s.name = 'TimeZonePicker' s.version = '1.3.0' - s.summary = 'A TimeZonePicker UIViewController similar to the iOS Settings app.' + s.summary = 'A TimeZonePicker UIViewController and SwiftUI View similar to the iOS Settings app.' s.description = <<-DESC * A ready view controller that can be used in any iOS app. * Search/filter functionality ready using a search bar. * Selection invokes a delegate with the selected TimeZone. + * SwiftUI implementation DESC s.homepage = 'https://github.com/gligorkot/TimeZonePicker' s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } s.author = { 'Gligor Kotushevski' => 'gligorkot@gmail.com' } s.social_media_url = 'https://twitter.com/gligor_nz' - s.platform = :ios, '8.0' - s.ios.deployment_target = '8.0' + s.platform = :ios, '13.0' + s.ios.deployment_target = '13.0' s.source = { :git => 'https://github.com/gligorkot/TimeZonePicker.git', :tag => s.version.to_s } - s.source_files = 'Classes', 'Classes/*.{swift}' + s.source_files = 'Classes', 'Classes/*.{swift}', 'Classes/SwiftUI/*.{swift}' s.resources = 'Resources/*.{json,storyboard}' s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.2' } - s.frameworks = 'UIKit' + s.frameworks = 'UIKit', 'SwiftUI' s.requires_arc = true s.swift_versions = ['4.0', '4.1', '4.2', '5.0', '5.1', '5.2'] diff --git a/TimeZonePickerExample/Podfile b/TimeZonePickerExample/Podfile index a00b7bf..1487e9f 100644 --- a/TimeZonePickerExample/Podfile +++ b/TimeZonePickerExample/Podfile @@ -6,7 +6,7 @@ workspace 'TimeZonePickerExample' target :'TimeZonePickerExample' do project 'TimeZonePickerExample.xcodeproj' - platform :ios, '8.0' + platform :ios, '13.0' pod 'TimeZonePicker', :path => '../' end