SwiftUser
SwiftUser

Reputation: 615

Can a DatePicker display year only?

Is it possible to use Apple's very own date picker and display just the year only?

This is my current code:

DatePicker("", selection: $cameraViewModel.age, displayedComponents: .date)
    .datePickerStyle(CompactDatePickerStyle())

Of course the .displayedComponents would allow me to alter the UI/elements however, there's no call for just the year.

I have searched Stackoverflow, found a thread, but the OP never followed up.

Upvotes: 7

Views: 6782

Answers (2)

Codelaby
Codelaby

Reputation: 2893

I created this YearPicker SwiftUI component

Observations of code: you need to create year, month, day, hour, minute, seconds, nanoseconds and time zone for .tag in Picker, correctly assign the initial value to select.

Any help to optimize is appreciated.

YearPicker

import SwiftUI

struct YearPicker: View {
    
    @Binding var selectedDate: Date
    let yearRange: ClosedRange<Int>
    
    init(selection: Binding<Date>, yearRange: ClosedRange<Int> = 1900...2060) {
        self._selectedDate = selection
        self.yearRange = yearRange
        
        // Ajustar el año seleccionado dentro del rango
        let calendar = Calendar.current
        let year = calendar.component(.year, from: selection.wrappedValue)
        if !yearRange.contains(year) {
            var adjustedYear = year
            if year < yearRange.lowerBound {
                adjustedYear = yearRange.lowerBound
            } else if year > yearRange.upperBound {
                adjustedYear = yearRange.upperBound
            }
            let originalComponents = calendar.dateComponents([.month, .day, .hour, .minute, .second, .nanosecond, .timeZone], from: selection.wrappedValue)
            var components = DateComponents()
            components.year = adjustedYear
            components.month = originalComponents.month
            components.day = originalComponents.day
            components.hour = originalComponents.hour
            components.minute = originalComponents.minute
            components.second = originalComponents.second
            components.nanosecond = originalComponents.nanosecond
            components.timeZone = originalComponents.timeZone
            self._selectedDate = Binding.constant(calendar.date(from: components)!)
        }
    }
    
    var body: some View {
        VStack {
            Picker("Year", selection: $selectedDate) {
                ForEach(yearRange, id: \.self) { year in
                    Text(displayYear(year: year))
                        .tag(self.dateFromYear(year))
                }
            }
        }
    }
        
    private func displayYear(year: Int) -> String {
        let numberFormatter: NumberFormatter = {
            let nf = NumberFormatter()
            nf.usesGroupingSeparator = false
            return nf
        }()
        return numberFormatter.string(from: NSNumber(value: year)) ?? "\(year)"
    }
    
    private func dateFromYear(_ year: Int) -> Date {
        let calendar = Calendar.current
        let originalComponents = calendar.dateComponents([.month, .day, .hour, .minute, .second, .nanosecond, .timeZone], from: selectedDate)
        var components = DateComponents()
        components.year = year
        components.month = originalComponents.month
        components.day = originalComponents.day
        components.hour = originalComponents.hour
        components.minute = originalComponents.minute
        components.second = originalComponents.second
        components.nanosecond = originalComponents.nanosecond
        components.timeZone = originalComponents.timeZone
        return calendar.date(from: components)!
    }
}

Preview

You can specific range value, default 1900...2060

#Preview {
    struct PreviewWrapper: View {
        @State var value: Date = Date()
        
        var body: some View {
            VStack {
                YearPicker(selection: $value)
                    .onChange(of: value) { oldValue, newValue in
                        print("chanege:", value)
                    }
                Text(value.description)
            }
        }
    }
    return PreviewWrapper()
}

You can see in action in short youtube YearPicker en SwiftUI

Upvotes: 0

pawello2222
pawello2222

Reputation: 54601

I don't think you can do this with a DatePicker - it's for selecting a Date and a year is just a simple Int.

Displaying a year only can be achieved with a standard Picker (although the appearance may differ):

struct ContentView: View {
    @State var selection = Date()
    var body: some View {
        Picker("", selection: $selection) {
            ForEach(2000...2020, id: \.self) {
                Text(String($0))
            }
        }
        .pickerStyle(InlinePickerStyle())
    }
}

Upvotes: 7

Related Questions