How do I change the appearance of the DatePicker in the SwiftUI framework to only months and years?
As others have already commented You would need to implement an HStack with two Pickers:
struct ContentView: View {
@State var monthIndex: Int = 0
@State var yearIndex: Int = 0
let monthSymbols = Calendar.current.monthSymbols
let years = Array(Date().year..<Date().year+10)
var body: some View {
GeometryReader{ geometry in
HStack(spacing: 0) {
Picker(selection: self.$monthIndex.onChange(self.monthChanged), label: Text("")) {
ForEach(0..<self.monthSymbols.count) { index in
Text(self.monthSymbols[index])
}
}.frame(maxWidth: geometry.size.width / 2).clipped()
Picker(selection: self.$yearIndex.onChange(self.yearChanged), label: Text("")) {
ForEach(0..<self.years.count) { index in
Text(String(self.years[index]))
}
}.frame(maxWidth: geometry.size.width / 2).clipped()
}
}
}
func monthChanged(_ index: Int) {
print("\(years[yearIndex]), \(index+1)")
print("Month: \(monthSymbols[index])")
}
func yearChanged(_ index: Int) {
print("\(years[index]), \(monthIndex+1)")
print("Month: \(monthSymbols[monthIndex])")
}
}
You would need this helper from this post to monitor the Picker changes
extension Binding {
func onChange(_ completion: @escaping (Value) -> Void) -> Binding<Value> {
.init(get:{ self.wrappedValue }, set:{ self.wrappedValue = $0; completion($0) })
}
}
And this calendar helper
extension Date {
var year: Int { Calendar.current.component(.year, from: self) }
}
How do I create a DatePicker in swiftUI that only select years and I want this to be in prespecified range of years?
You can create your custom year picker by using a simple Picker
and passing it an array of Int
as your years.
struct YearPicker: View {
private let numberFormatter: NumberFormatter = {
let nf = NumberFormatter()
nf.usesGroupingSeparator = false
return nf
}()
@State private var selectedYearIndex: Int
private let years: [Int]
init(start: Int, end: Int) {
let years = Array(start...end)
self._selectedYearIndex = State(initialValue: years.startIndex)
self.years = years
}
func yearString(at index: Int) -> String {
let selectedYear = years[index]
return numberFormatter.string(for: selectedYear) ?? selectedYear.description
}
var body: some View {
VStack {
Text("Your birth year is \(yearString(at: selectedYearIndex))")
Picker("Year", selection: $selectedYearIndex) {
ForEach(years.indices) { yearIndex in
Text("\(self.yearString(at: yearIndex))")
}
}
}
}
}
struct YearPicker_Previews: PreviewProvider {
static var previews: some View {
YearPicker(start: 2000, end: 2020)
}
}
UIDatePicker select Month and Year
Yeah, you probably want to just make your own picker. You don't have to subclass it or anything, though; just use a generic UIPickerView
and return appropriate values from your UIPickerViewDelegate
/UIPickerViewDataSource
methods.
Fetching and setting date from datepicker, but still getting old default value
Reply against your second question about wrapper properties used in SwiftUI i.e @State, @Binding, @Published.
The most common @Things used in SwiftUI are:
• @State - Binding<Value>
• @Binding - Binding<Value>
• @ObservedObject - Binding<Value> (*)
• @EnvironmentObject - Binding<Value> (*)
• @Published - Publisher<Value, Never>
(*) technically, we get an intermediary value of type Wrapper, which turns a Binding once we specify the keyPath to the actual value inside the object.
So, as you can see, the majority of the property wrappers in SwiftUI, namely responsible for the view’s state, are being “projected” as Binding, which is used for passing the state between the views.
The only wrapper that diverges from the common course is @Published, but:
1. It’s declared in Combine framework, not in SwiftUI
2. It serves a different purpose: making the value observable
3. It is never used for a view’s variable declaration, only inside ObservableObject
Consider this pretty common scenario in SwiftUI, where we declare an ObservableObject and use it with @ObservedObject attribute in a view:
class ViewModel: ObservableObject {
@Published var value: Int = 0
}
struct MyView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View { ... }
}
MyView can refer to $viewModel.value and viewModel.$value - both expressions are correct. Quite confusing, isn’t it?
These two expressions ultimately represent values of different types: Binding and Publisher, respectively.
Both have a practical use:
var body: some View {
OtherView(binding: $viewModel.value) // Binding
.onReceive(viewModel.$value) { value // Publisher
// do something that does not
// require the view update
}
}
Hope it may help you.
Unable to adjust the value of datepicker for UI testing: PickerWheel of type 6
There still doesn't seem to be a supported method of changing the new date picker wheels as of Xcode 12.1.
I've come up with this method to get around the issue:
func adjustDatePicker(wheel: XCUIElement, to newValue: String) -> Bool {
let x = wheel.frame.width / 2.0
let y = wheel.frame.height / 2.0
// each wheel notch is about 30px high, so tapping y - 30 rotates up. y + 30 rotates down.
var offset: CGFloat = -30.0
var reversed = false
let previousValue = wheel.value as? String
while wheel.value as? String != newValue {
wheel.coordinate(withNormalizedOffset: .zero).withOffset(CGVector(dx: x, dy: y + offset)).tap()
let briefWait = expectation(description: "Wait for wheel to rotate")
briefWait.isInverted = true
wait(for: [briefWait], timeout: 0.25)
if previousValue == wheel.value as? String {
if reversed {
// we already tried reversing, can't find the desired value
break
}
// we didn't move the wheel. try reversing direction
offset = 30.0
reversed = true
}
}
return wheel.value as? String == newValue
}
SwiftUI Picker onChange or equivalent?
Deployment target of iOS 14 or newer
Apple has provided a built in onChange
extension to View
, which can be used like this:
struct MyPicker: View {
@State private var favoriteColor = 0
var body: some View {
Picker(selection: $favoriteColor, label: Text("Color")) {
Text("Red").tag(0)
Text("Green").tag(1)
}
.onChange(of: favoriteColor) { tag in print("Color tag: \(tag)") }
}
}
Deployment target of iOS 13 or older
struct MyPicker: View {
@State private var favoriteColor = 0
var body: some View {
Picker(selection: $favoriteColor.onChange(colorChange), label: Text("Color")) {
Text("Red").tag(0)
Text("Green").tag(1)
}
}
func colorChange(_ tag: Int) {
print("Color tag: \(tag)")
}
}
Using this helper
extension Binding {
func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
return Binding(
get: { self.wrappedValue },
set: { selection in
self.wrappedValue = selection
handler(selection)
})
}
}
Related Topics
Differences Between Ways of Initializing a Dictionary
Swift Generics and Protocols Not Working on Uikit [Possible Bug]
Use Huge Numbers in Apple Swift
Filtering Dictionary in Swift 4 Fails in Xcode, But Succeeds in Playground
Get Images from Document Directory Not File Path Swift 3
How to Set Up Multiple Combine Timer Publishers
Xcode Firebase | Cannot Convert Value of Type'Authdataresult' to Expected Argument Type 'User'
What Is the Shortest Way to Run Same Code N Times in Swift
Auth.Auth().Currentuser.Reload() Doesn't Refresh Currentuser.Isemailverified
Swift Conditional Conformance with Any
Xcode Gm: No Swift Language for Os X Command Line Tool Project
Raw Value of Enumeration, Default Value of a Class/Structure, What's the Different
Uicollectionviewcell Not Forcing Cell Size
How to Make Text Typed in Textfield Undeletable
Why Won't My Collection View Cells Display in the iPhone Simulator