Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions Classes/CityCountryTimeZone.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
53 changes: 53 additions & 0 deletions Classes/SwiftUI/TimeZoneDataManager.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
26 changes: 26 additions & 0 deletions Classes/SwiftUI/TimeZonePreviewSelectionView.swift
Original file line number Diff line number Diff line change
@@ -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)
}

}
}
41 changes: 41 additions & 0 deletions Classes/SwiftUI/TimeZoneSelectorView.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<a href='https://ko-fi.com/gligor' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

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

Expand Down Expand Up @@ -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.
Expand Down
Binary file added Screenshots/SwiftUI_macOS_picker.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Screenshots/SwiftUI_macOS_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 6 additions & 5 deletions TimeZonePicker.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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']

Expand Down
2 changes: 1 addition & 1 deletion TimeZonePickerExample/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ workspace 'TimeZonePickerExample'

target :'TimeZonePickerExample' do
project 'TimeZonePickerExample.xcodeproj'
platform :ios, '8.0'
platform :ios, '13.0'

pod 'TimeZonePicker', :path => '../'
end