Is there a way to have a UIDatePicker show year only?
I have a little code that I was about to delete, but the snippet is better off if it is online somewhere. It's not amazing, but it is searchable!
Objective-C code to create an array of all years since 1960. Perfect for input into a UIPicker
//Get Current Year into i2
NSDateFormatter* formatter = [[[NSDateFormatter alloc] init]autorelease];
[formatter setDateFormat:#"yyyy"];
int i2 = [[formatter stringFromDate:[NSDate date]] intValue];
//Create Years Array from 1960 to This year
years = [[NSMutableArray alloc] init];
for (int i=1960; i<=i2; i++) {
[years addObject:[NSString stringWithFormat:#"%d",i]];
}
The UIPickerView data source and delegate methods:
- (NSInteger)numberOfComponentsInPickerView: (UIPickerView*)thePickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component
{
return [years count];
}
- (NSString *)pickerView:(UIPickerView *)thePickerView
titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return [years objectAtIndex:row];
}
Don't forget the declaration in the interface
//Data
NSMutableArray *years;
The out put of it is
Referenced from here
You can't do this with UIDatePicker, but you can with UIPicker.
You need to create an array for the years, and add them to the UIPicker using the UIPicker delegates
Here's a tutorial.
I cannot comment, but I was playing around with the answer by Wolvorin and I decided to do a way where the most recent year would be at the top of the Picker. Currently, his code goes oldest to most recent years.
All I changes was in my NSMutableArray setup in the viewDidLoad instead of his:
//Create Years Array from 1960 to This year
years = [[NSMutableArray alloc] init];
for (int i=1960; i<=i2; i++) {
[years addObject:[NSString stringWithFormat:#"%d",i]];
}
I used:
years = [[NSMutableArray alloc] init];
for (int i1=i2; i1<=i2 & i1>=1920; i1--) {
[years addObject:[NSString stringWithFormat:#"%d",i1]];
}
Which does the for in reversed order going from the most recent date, then subtracting one year (and adding it to the Array) until it gets to 1920.
My formula:
int i1=i2; i1<=i2 & i1>=1920; i1--
Just updating the existing answer in this post with Swift 3:
var yearsTillNow : [String] {
var years = [String]()
for i in (1970..<2018).reversed() {
years.append("\(i)")
}
return years
}
Just use this for your UIPickerView datasource.
It is not possible to set only Year and Month. Only these modes are available as of iOS 13.5.1
time: A mode that displays the date in hours, minutes, and (optionally) an AM/PM designation. The exact items shown and their
order depend upon the locale set. An example of this mode is [ 6 | 53
| PM ].
date: A mode that displays the date in months, days of the month, and years. The exact order of these items depends on the locale
setting. An example of this mode is [ November | 15 | 2007 ].
dateAndTime: A mode that displays the date as unified day of the week, month, and day of the month values, plus hours, minutes, and
(optionally) an AM/PM designation. The exact order and format of these
items depends on the locale set. An example of this mode is [ Wed Nov
15 | 6 | 53 | PM ].
countDownTimer: A mode that displays hour and minute values, for example [ 1 | 53 ]. The application must set a timer to fire at the
proper interval and set the date picker as the seconds tick down. UIDatePicker.Mode - developer.apple.com
You may build it using a custom UIPickerView.
class DatePickerViewController: UIViewController {
var dates = [Date]()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
var date = Date().addYear(n: -10)
let endDate = Date().addYear(n: 10)
repeat {
date = date.addMonth(n: 1)
dates.append(date)
} while date < endDate
let picker = UIPickerView()
picker.dataSource = self
picker.delegate = self
picker.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(picker)
picker.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
picker.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
picker.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
picker.heightAnchor.constraint(equalToConstant: 500).isActive = true
}
}
extension DatePickerViewController: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.dates.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
let date = self.dates[row]
return date.stringDate()
}
func view(forRow row: Int, forComponent component: Int) -> UIView? {
let label = UILabel()
return label
}
}
extension DatePickerViewController: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let date = self.dates[row]
// Date that the user select.
print( date, date.stringDate())
}
}
extension Date {
public func addYear(n: Int) -> Date {
let calendar = Calendar.current
return calendar.date(byAdding: .year, value: n, to: self)!
}
public func addMonth(n: Int) -> Date {
let calendar = Calendar.current
return calendar.date(byAdding: .month, value: n, to: self)!
}
public func stringDate() -> String {
let calendar = Calendar.current
let dateFormatter = DateFormatter()
dateFormatter.timeZone = calendar.timeZone
dateFormatter.locale = calendar.locale
// Use YYYY to show year only.
// Use MMMM to show month only.
dateFormatter.setLocalizedDateFormatFromTemplate("MMMM YYYY")
let dateString = dateFormatter.string(from: self)
return dateString
}
}
Just change the following two lines- add/subtract as many years you wish to show.
var date = Date().addYear(n: -10)
let endDate = Date().addYear(n: 10)
I changed Sharukh Mastan's code slightly to always show the current year on top.
var formattedDate: String? = ""
let format = DateFormatter()
format.dateFormat = "yyyy"
formattedDate = format.string(from: date)
var yearsTillNow: [String] {
var years = [String]()
for i in (Int(formattedDate!)!-70..<Int(formattedDate!)!+1).reversed() {
years.append("\(i)")
}
return years
}
print(yearsTillNow)
This prints an array of years going back for 70 years which you can use as UIPickerView datasource
Related
In my case, I am trying to customise FSCalendar. Here, add multiple event in calendar options available but it is showing three dots. I need to do If two same date in the array need to show two dot also if three need to show three dot but above three also need to show only three dot. How to achieve this?
https://iosexample.com/a-fully-customizable-ios-calendar-library-compatible-with-objective-c-and-swift/
My Code
var datesWithEvent = ["02-09-2019", "03-09-2019", "07-09-2019", "09-09-2019"]
var datesWithMultipleEvents = ["03-09-2019", "03-09-2019", "02-09-2019", "09-09-2019"]
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
let dateString = self.dateFormatter.string(from: date)
if self.datesWithEvent.contains(dateString) {
return 1
}
if self.datesWithMultipleEvents.contains(dateString) {
return 3
}
return 0
}
func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, eventDefaultColorsFor date: Date) -> [UIColor]? {
let key = self.dateFormatter.string(from: date)
if self.datesWithMultipleEvents.contains(key) {
return [UIColor.magenta, appearance.eventDefaultColor, UIColor.black]
}
return nil
}
Here,Below answer for adjusting the FSCalendar dot UI count based on JSON array Data.
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
let dateString = self.dateFormatter.string(from: date)
if self.datesWithEvent.contains(dateString) {
return 1 // Here, We have to assign JSON count key value based on duplication it will increase dot count UI.
}
return 0
}
I am only new to all this so forgive my ignorance! I have a Table View to which I call a cell update method every time I open the Scene (it's sorted by date). I am trying to dynamically show a different date format depending on the when the Object was last edited. Example: Show time if date is today, Show "Yesterday" if date was yesterday etc. My question is - What is the best way to write this and is calling the below func in my cell.update call going to be memory intensive? How can I write this better? Any help would be greatly appreciated!
func update(with fitnessInfo: Fitness) {
if let date = fitnessInfo.dateEdited as Date? {
let today = Calendar.current.isDateInToday(date)
let yesterdayFunc = Calendar.current.isDateInYesterday(date)
let yesterday = date.addingTimeInterval(172800)
let withinSevenDays = date.addingTimeInterval(604800)
let dateFormatter = DateFormatter()
func setdate(_ dateFormatter: DateFormatter) {
let convertedDate = dateFormatter.string(from: date)
fitnessDateLabel.text = convertedDate
}
if today == true {
dateFormatter.dateFormat = "h:mm a"
setdate(dateFormatter)
} else if yesterdayFunc == true {
fitnessDateLabel.text = "Yesterday"
} else {
switch date {
case yesterday...withinSevenDays:
dateFormatter.dateFormat = "EEEE"
setdate(dateFormatter)
default:
dateFormatter.dateFormat = "MMMM dd, yyyy"
setdate(dateFormatter)
}
}
}
Here is the Table View func (cellForRowAt) which this is called from.
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: “fitnessCell", for: indexPath) as! FitnessTableViewCell
let fitnessInfo = fetchedRC.object(at: indexPath)
cell.update(with: fitnessInfo)
return cell
}
If code works it can not be bad.
It's a good idea to write unit- and performance-tests before any performance optimization. So you will see that your changes don't affect result and give performance improvement. Also you can use Xcode instruments to understand how exactly your code use CPU and memory by click Product->Profile and select Time profile and Allocations instruments. Don't forget that performance optimization is often a tradeoff between memory and CPU usage.
You should separate logic from presentation. So you be able to write unit-test. Also you can retain DateFrommater statically instead of initialize it each time you draw a cell. Here is an example how it can be done:
final class DateMapper {
static let todayDateFormatter = DateFormatter()
static let lastWeekAgoDateFormatter = DateFormatter()
static let moreThanWeekAgoDateFormatter = DateFormatter()
let today: Date
let currentCalendar = Calendar.current
init(today: Date) {
self.today = today
DateMapper.todayDateFormatter.dateFormat = "h:mm a"
DateMapper.lastWeekAgoDateFormatter.dateFormat = "EEEE"
DateMapper.moreThanWeekAgoDateFormatter.dateFormat = "MMMM dd, yyyy"
}
func formattedDate(date: Date) -> String {
guard let totalDays = currentCalendar.dateComponents([.day], from: date, to: today).day else {
return ""
}
switch totalDays {
case 0:
return DateMapper.todayDateFormatter.string(from: date)
case 1:
return "Yesterday"
case 2...7:
return DateMapper.lastWeekAgoDateFormatter.string(from: date)
default:
return DateMapper.moreThanWeekAgoDateFormatter.string(from: date)
}
}
}
This class can be used at your update method like this:
func update(with fitnessInfo: Fitness) {
let dateMapper = DateMapper(today: Date())
fitnessDateLabel.text = dateMapper.formattedDate(date: fitnessInfo.dateEdited)
}
Or you can retain DateMapper at ViewController and use it directly at cellForRowAt.
This class can be covered with unit test:
class ExampleTests: XCTestCase {
var dateMapper: DateMapper {
let todayStub = dateStubByComponents(year: 2019, month: 5, day: 4, hour: 12)
return DateMapper(today: todayStub)
}
func testTodayDate() {
// arrange
let todayStub = dateStubByComponents(year: 2019, month: 5, day: 4, hour: 11)
// act
let result = dateMapper.formattedDate(date: todayStub)
// assert
XCTAssertEqual(result, "11:00 AM")
}
func testYestadayDate() {
// arrange
let yesterdayStub = dateStubByComponents(year: 2019, month: 5, day: 3, hour: 0)
// act
let result = dateMapper.formattedDate(date: yesterdayStub)
// assert
XCTAssertEqual(result, "Yesterday")
}
func testLastWeekDate() {
// arrange
let lastWeekStub = dateStubByComponents(year: 2019, month: 5, day: 2, hour: 0)
// act
let result = dateMapper.formattedDate(date: lastWeekStub)
// assert
XCTAssertEqual(result, "Thursday")
}
func testPerformanceExample() {
let lastWeekStub = dateStubByComponents(year: 2019, month: 5, day: 2, hour: 0)
self.measure {
_ = dateMapper.formattedDate(date: lastWeekStub)
}
}
// MARK: Private
private func dateStubByComponents(year: Int, month: Int, day: Int, hour: Int) -> Date {
var dateStubComponents = DateComponents()
dateStubComponents.year = year
dateStubComponents.month = month
dateStubComponents.day = day
dateStubComponents.hour = hour
guard let dateStub = Calendar.current.date(from: dateStubComponents) else { fatalError("Date must be valid") }
return dateStub
}
}
At console you will see how long will take to transform Date to String:
values: [0.000922, 0.000063, 0.000041, 0.000038, 0.000036, 0.000035,
0.000035, 0.000036, 0.000043, 0.000027]
So in my app, users can create expenses and store and view them in a per month basis. Currently, all my expense appear in the order I add them, but I want them to appear in the order of dates and I also want to have a tableviewheader with the dates so it groups the expenses together. I tried this code in my viewdidLoad.
for expense in monthlyExpenses{
if let dateString = expense.modificationDate?.convertToString(){
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = DateFormatter.Style.medium
dateFormatter.timeStyle = DateFormatter.Style.none
let finalDate = dateFormatter.date(from: dateString)
if let finalDate = finalDate{
let dateString2 = finalDate.timeIntervalSinceReferenceDate
if self.toDoList[dateString2] == nil {
self.toDoList[dateString2] = [expense.name!]
}
else {
self.toDoList[dateString2]?.append(expense.name!)
}
}
}
}
and then tried this code in my tableviewheader section, however get a EXC_BAD_INSTRUCTION error.
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let dateDouble = self.sortedSections[section]
let date = Date(timeIntervalSinceReferenceDate: dateDouble)
let dateformatter = DateFormatter()
dateformatter.dateStyle = DateFormatter.Style.medium
dateformatter.timeStyle = DateFormatter.Style.none
return dateformatter.string(from: date)
}
Each expense is an individual object that is stored in core data and has properties such as name, amount, modification date and etc. I want them to be sorted by modification Date. How will I go about doing this?
1. Create a dictionary which contains date as a key and array of orders modified or placed on that as value.
var orderDictionary[Date:[Order]?] = [:]
for order in yourOrders {
var orders = orderDictionary[order.modificationDate] ?? []
if orders.count == 0 {
orderDictionary[order.modificationDate] = [order]
} else {
orders.append(order)
orderDictionary[order.modificationDate] = orders
}
}
2. Sort key array from dictionary.
let dateArray = orderDictionary.keys
dateArray.sort { $0 < $1 }
Change compare condition based on your requirement like whether you want it in ascending or in descending order.
3. In func tableView(UITableView, titleForHeaderInSection: Int) write
return dateArray[section] or format date as you want.
4. In numberOfRowsForSection write
return orderDictionary[dateArray[section]].count
5. In cellForRowAtIndexPath write
let order = orderDictionary[dateArray[indexPath.section]][indexPath.row]
I have done something similar, But I am using a library that already has xib and methods to adjust everything. Here is just a small help from my side.
You can prepare one xib to show dates and data in tableView. Here, you can hide the row if next date is same as previous date and show whenever the date changes.
To show data according to dates you can do something like this (If data is added according to dates)
let expenses = [ExpensesClass]()
// in `cellForItemAt indexPath`
let expense = expenses[indexPath.row]
let date = expense.date
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM d, yyyy"
let dateString = dateFormatter.string(from: date! as Date)
. . . . . .
if indexPath.row > 0 {
let previousExpensesData = expenses[indexPath.row - 1].date
let day = Calendar.current.component(.day, from: expense.date) // Do not add above 'date' value here, you might get some garbage value. I know the code is redundant. You can adjust that.
let previousDay = Calendar.current.component(.day, from: expenses[indexPath.row - 1].date)
if day == previousDay {
cell.dateTitle.text = ""
} else {
cell.dateTitle.text = dateString
}
}else{
cell.dateTitle.text = dateString
print("Cannot return previous Message Index...\n\n")
}
You can adjust height in this method -> func tableView(UITableView, heightForRowAt: IndexPath).
I am not sure about this method -> func tableView(UITableView, titleForHeaderInSection: Int) . You can try it in this method as well if the above one doesn't work for you and adjust the height accordingly in -> func tableView(UITableView, titleForHeaderInSection: Int)
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat
{
let expense = expenses[indexPath.row]
if indexPath.row > 0{
let day = Calendar.current.component(.day, from: expense.date)
let previousDay = Calendar.current.component(.day, from: expense[indexPath.row - 1].date)
if day == previousDay {
return 0.0
}else{
return 20.0
}
}
return 20.0
}
This is the picker that contains the day, date, and month as input, also, in one scroll, is there any way to achieve this kind of functionality from picker/datepicker in iOS?
For such a case, you should add it as a UIPickerView. Assuming that the issue would be: How to fill its values by the days of the current month? to make it more clear, I would mention it as steps:
You should get the current month start and end dates. You could find how to achieve it by checking this answer.
You will need to get all days -with the desired format- between the start and the end dates. You could find how to achieve it by checking this answer.
After getting all days in the current month, you could fill the date picker datasource method easily.
Implementation:
class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
var currentMonthDays: [String] {
var array = [String]()
var date = Date().startOfMonth() // first date
let endDate = Date().endOfMonth()
let fmt = DateFormatter()
//fmt.dateFormat = "dd/MM/yyyy"
fmt.dateFormat = "EEE dd MMM"
while date <= endDate {
date = Calendar.current.date(byAdding: .day, value: 1, to: date)!
array.append(fmt.string(from: date))
}
return array
}
override func viewDidLoad() {
super.viewDidLoad()
print(currentMonthDays)
}
// Picker View Data Soruce & delegate Methods
// REMARK: make sure to connect pickerview datasource/delegate to your view controller,
// I did it from the storyboard
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return currentMonthDays.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return currentMonthDays[row]
}
}
// Step #1:
extension Date {
func startOfMonth() -> Date {
return Calendar.current.date(from: Calendar.current.dateComponents([.year, .month], from: Calendar.current.startOfDay(for: self)))!
}
func endOfMonth() -> Date {
return Calendar.current.date(byAdding: DateComponents(month: 1, day: -1), to: self.startOfMonth())!
}
}
Do below steps :-
1. You have to change date with applying DateFormatter().
2. Save all dates (string format) in your NSArray or NSMutableArray for your UIPickerView Datasource method.
3. Then call UIPickerView Datasource and Delegate methods for fetch your result.
For date conversion you can apply below code.
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEE dd MMM"
let currentDate = NSDate()
let convertedDateString = dateFormatter.string(from:currentDate as Date)
print("convertedDate: ", convertedDateString)
I am using FSCalendar to add calendar UI in my project . But I am unable to find any solution to add events to the calendar. So any ideas on how to add events to FSCalendar or any third party calendar framework?
To Set Events Base On Dates..
Instance of DateFormatter And Variables:
var datesWithEvent = ["2015-10-03", "2015-10-06", "2015-10-12", "2015-10-25"]
var datesWithMultipleEvents = ["2015-10-08", "2015-10-16", "2015-10-20", "2015-10-28"]
fileprivate lazy var dateFormatter2: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
next is... DataSource Function:
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
let dateString = self.dateFormatter2.string(from: date)
if self.datesWithEvent.contains(dateString) {
return 1
}
if self.datesWithMultipleEvents.contains(dateString) {
return 3
}
return 0
}
I Hope This Help You, This Code is Base On FsCalendar Sample Project.
I have an array that contains events's dates and the number of event on this date what I did is:
1- Comparing dates(comparing dates without time using compareDate(data.eventDate!, toDate: date, toUnitGranularity: .Day)) from my array with date(in the function)
2- If they're equal I'll add date to an array(datesWithEvent) without time so I can compare it later without problems
3- return true when it finds the right dates
var datesWithEvent:[NSDate] = []
func calendar(calendar: FSCalendar, hasEventForDate date: NSDate) -> Bool {
for data in eventsArray{
let order = NSCalendar.currentCalendar().compareDate(data.eventDate!, toDate: date, toUnitGranularity: .Day)
if order == NSComparisonResult.OrderedSame{
let unitFlags: NSCalendarUnit = [ .Day, .Month, .Year]
let calendar2: NSCalendar = NSCalendar.currentCalendar()
let components: NSDateComponents = calendar2.components(unitFlags, fromDate: data.eventDate!)
datesWithEvent.append(calendar2.dateFromComponents(components)!)
}
}
return datesWithEvent.contains(date)
}
And to precise the number of event on this date (different number of dots) I've added this code
func calendar(calendar: FSCalendar, numberOfEventsForDate date: NSDate) -> Int {
for data in eventsArray{
let order = NSCalendar.currentCalendar().compareDate(data.eventDate!, toDate: date, toUnitGranularity: .Day)
if order == NSComparisonResult.OrderedSame{
return data.numberOfEvent!
}
}
return 0
}
Try this code,in Objective-C
- (NSInteger)calendar:(FSCalendar *)calendar numberOfEventsForDate:(NSDate*)date
{
NSString *dateString = [calendar stringFromDate:date format:#"yyyy-MM-dd"];
if ([_datesWithEvent containsObject:dateString]){
return 1;
}
else{
NSLog(#"........Events List........");
}
return 0;
}
try these code it will help you
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
let dateString = self.dateFormatter.string(from: date)
print(dateString)
// datesForEvents : array of dates
if self.datesForEvents.contains(dateString) {
return 1
}
return 0