Group days of the week with same values in Swift - arrays

I have a service request with schedules for a shop in an array called "hoursArray" with this format:
"hours": [
      "00:00-23:59",
      "00:00-23:59",
      "00:00-21:59",
      "00:00-21:59",
      "00:00-22:59",
      "00:00-22:59",
      "00:00-23:59"
    ]
I show this information in an horizontal stack view that contains two verticals stack views, one with labels for the seven days of the week and another one with seven labels for the schedule for that day, I fill this labels with this function:
func getSchedule(){
scheduleLabel1.text = hoursArray[0] as? String
scheduleLabel2.text = hoursArray[1] as? String
scheduleLabel3.text = hoursArray[2] as? String
scheduleLabel4.text = hoursArray[3] as? String
scheduleLabel5.text = hoursArray[4] as? String
scheduleLabel6.text = hoursArray[5] as? String
scheduleLabel7.text = hoursArray[6] as? String
dayLabel1.text = "Monday"
dayLabel2.text = "Tuesday"
dayLabel3.text = "Wednesday"
dayLabel4.text = "Thursday"
dayLabel5.text = "Friday"
dayLabel6.text = "Saturday"
dayLabel7.text = "Sunday"
}
What I need is to group correlative days to show it when I have a response of the request with days with the same value, for example if Monday and Tuesday have the same value I will show "Monday - Tuesday" in day1Label.text and its schedule in scheduleLabel1.text, how could I do that?

You can use following function to format
let hours = [
"00:00-23:59",
"00:00-23:59",
"00:00-21:59",
"00:00-21:59",
"00:00-22:59",
"00:00-22:59",
"00:00-23:59"
]
let days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
func format() -> [(String, String)] {
var temp: [(String, String)] = []
for i in 0..<hours.count {
temp.append((days[i], hours[i]))
}
var result: [(String, String)] = []
var index = 0
var dayStart: String?
var dayEnd: String?
var time: String?
while index < temp.count {
if dayStart == nil {
dayStart = temp[index].0
time = temp[index].1
}
if (index != temp.count - 1) && (temp[index].1 == temp[index+1].1) {
dayEnd = temp[index+1].0
index += 1
}
else {
if let start = dayStart, let tm = time {
if let end = dayEnd {
result.append(("\(start)-\(end)", tm))
dayEnd = nil
}
else {
result.append((start, tm))
}
dayStart = nil
time = nil
}
index += 1
}
}
return result
}
From this you will get formatted result and you can then pass these as an input to TableView or IBOutletCollection. Using static labels for these is not recommended as base on groups numbers of labels may change b/w 1-7. So it's better to use Tableview.

One way to do it is like this, first grouping the data by hour range:
let hourRanges = [
"00:00-23:59",
"00:00-23:59",
"00:00-21:59",
"00:00-21:59",
"00:00-22:59",
"00:00-22:59",
"00:00-23:59"
]
let weekDays = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
]
struct WeekDaysHourRange {
let hourRange: String
var weekDays: [String]
}
var daysRanges: [WeekDaysHourRange] = []
// assuming both arrays have the same length, otherwise checks are needed
var previousWeekDayRange: WeekDaysHourRange?
for i in 0...6 {
let hourRange = hourRanges[i]
let weekDay = weekDays[i]
if previousWeekDayRange?.hourRange == hourRange {
daysRanges[daysRanges.count - 1].weekDays.append(weekDay)
} else {
let newWeekDayRange = WeekDaysHourRange(hourRange: hourRange, weekDays: [weekDay])
previousWeekDayRange = newWeekDayRange
daysRanges.append(newWeekDayRange)
}
}
To update the UI, I would create to arrays of UILabels, to allow populating the labels with a for loop easilly, like this:
let scheduleLabels: [UILabel] = [
scheduleLabel1,
scheduleLabel2,
scheduleLabel3,
scheduleLabel4,
scheduleLabel5,
scheduleLabel6,
scheduleLabel7
]
let dayLabels: [UILabel] = [
dayLabel1,
dayLabel2,
dayLabel3,
dayLabel4,
dayLabel5,
dayLabel6,
dayLabel7
]
daysRanges.enumerated().forEach { (i, weekDayHourRange) in
var daysString: String
if weekDayHourRange.weekDays.count == 1 {
daysString = weekDayHourRange.weekDays.first!
} else {
daysString = "\(weekDayHourRange.weekDays.first!) - \(weekDayHourRange.weekDays.last!)"
}
dayLabels[i].text = daysString
scheduleLabels[i].text = weekDayHourRange.hourRange
}
Following Joakim Danielson comment, and If you only need to create the view once and not updated it, you can create the views on the fly:
daysRanges.enumerated().forEach { (i, weekDayHourRange) in
var daysString: String
if weekDayHourRange.weekDays.count == 1 {
daysString = weekDayHourRange.weekDays.first!
} else {
daysString = "\(weekDayHourRange.weekDays.first!) - \(weekDayHourRange.weekDays.last!)"
}
let dayLabel = UILabel()
dayLabel.text = daysString
let scheduleLabel = UILabel()
scheduleLabel.text = weekDayHourRange.hourRange
dayLabelsStackView.addArrangedSubview(dayLabel)
scheduleLabelsStackView.addArrangedSubview(scheduleLabel)
}
This would output something like this:
"Monday - Tuesday - 00:00-23:59"
"Wednesday - Thursday - 00:00-21:59"
"Friday - Saturday - 00:00-22:59"
"Sunday - 00:00-23:59"

There are already some decent answers, but reading this it seemed like a functional approach would be a good match for handling the inputs. This approach uses zip to combine the two datasets into an array of tuples and then reduce to consolidate them, with a bit of pattern matching to mangle the strings into the required shape. Finally it iterates through the resulting array with map to add the consolidated items to the stackviews.
let days = ["Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
]
let hours = [
"00:00-23:59",
"00:00-23:59",
"00:00-23:59",
"00:00-21:59",
"00:00-22:59",
"00:00-22:59",
"00:00-23:59"
]
zip(days, hours)
.reduce([(String, String)]()){
guard let last = $0.last else {return [$1]}
if last.1 == $1.1 {
var days = last.0
if let lastDayRange = days.range(of: #"(?<=- )[A-Z,a-z]*day"#, options: .regularExpression) {
days.replaceSubrange(lastDayRange, with: $1.0)
} else {
days = days + " - \($1.0)"
}
return $0.dropLast() + [(days, $1.1)]
} else {
return $0 + [$1]
}
}
.map{
let dayLabel = UILabel()
let hoursLabel = UILabel()
dayLabel.text = $0.0
hoursLabel.text = $0.1
daysStack.addArrangedSubview(dayLabel) //or whatever the stackview is called
hoursStack.addArrangedSubview(hoursLabel)
}
The UI part is simplistic, and more mght be required to get the desired output, but it should be enough to get started.

Related

swift check date is today or tomorrow

I have an integer array which is a collection of today's and tomorrow's dates, I want to separate the integer array based on the type of day
let dateCollection = [
1633722900,
1633730500,
1633754910,
1633758913,
1633820400,
1633824000,
1633827600,
1633831200,
1633834800,
1633838400,
1633842000
]
expected result
let today: [Int] = [
1633722900,
1633730500,
1633754910,
1633758913
]
let tomorrow: [Int] = [
1633820400,
1633824000,
1633827600,
1633831200,
1633834800,
1633838400,
1633842000
]
what should i do to separate them, i have made an extension to convert the integer to date or vice versa, and display it as a time, i already create the date to time extension too
func getTimetringFromINT() -> String {
let date = Date(timeIntervalSince1970: TimeInterval(self))
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "id")
dateFormatter.dateFormat = "HH:mm"
return dateFormatter.string(from: date)
}
selector i made, after convert the date to Time string
You can use Calendar.current.ordinality to compare the day of the year for the various dates. Here's an example that generates dates from today and tomorrow and then filters them back into separate arrays:
let today = Date()
let todayInts = Array(0..<10).map { today.timeIntervalSince1970 + TimeInterval($0) }
print("Today:", todayInts,"\n")
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today)!
let tomorrowInts = Array(0..<10).map { tomorrow.timeIntervalSince1970 + TimeInterval($0) }
print("Tomorrow:", tomorrowInts,"\n")
let allInts = todayInts + tomorrowInts
let todayDayOfYear = Calendar.current.ordinality(of: .day, in: .year, for: today)!
let filteredTodayInts = allInts.filter { Calendar.current.ordinality(of: .day, in: .year, for: Date(timeIntervalSince1970: $0)) == todayDayOfYear }
print("Filtered today:", filteredTodayInts,"\n")
let tomorrowDayOfYear = todayDayOfYear + 1
let filteredTomorrowInts = allInts.filter { Calendar.current.ordinality(of: .day, in: .year, for: Date(timeIntervalSince1970: $0)) == tomorrowDayOfYear }
print("Filtered tomorrow:", filteredTomorrowInts,"\n")
An efficient way to do this would be to calculate the int value of midnight between today and tomorrow and then split the array based on that value
let calendar = Calendar.current
var today = [Int]()
var tomorrow = [Int]()
if let midnight = calendar.date(byAdding: .day, value: 1, to: calendar.startOfDay(for: .now))?.timeIntervalSince1970 {
let limit = Int(midnight)
dateCollection.forEach { $0 < limit ? today.append($0) : tomorrow.append($0) }
}
Another approach is to get the start date of the next day (regardless of daylight saving time changes) with the Calendar API and then partition the array
let startOfTomorrow = Calendar.current.nextDate(after: Date(),
matching: DateComponents(hour: 0),
matchingPolicy: .nextTime)!
.timeIntervalSince1970
let splitIndex = dateCollection.partition{ $0 < Int(startOfTomorrow) }
let tomorrowDates = dateCollection[..<splitIndex]
let todayDates = dateCollection[splitIndex...]
To be able to run this declare dateCollection as var
By Getting StartOfDay of any Time, we can classify all Date that shares the same StartOfDay.
extension Date {
var morning: Date { Calendar.current.startOfDay(for: self) }
var nextMorning: Date { Calendar.current.date(byAdding: .day, value: 1, to: self)!.morning }
}
extension Int {
var since1970: Date { Date(timeIntervalSince1970: TimeInterval(self)) }
}
In this case:
let todayMorning = Date().morning // Date().advanced(by: 3600*24 * -19).morning
let tomorrowMorning = Date().nextMorning // Date().advanced(by: 3600*24 * -19).nextMorning
let dateCollection = [ ... ]
let today = dateCollection.filter{ $0.since1970.morning == todayMorning }
let tomorrow = dateCollection.filter{ $0.since1970.morning == tomorrowMorning }

Struct inside struct Equatable count duplicate item Swift

The answer I found is with one level of struct. In my code I want to find out how many same dates is in inside of the struct of the struct. I never use equatable before so I'm not sure how to implement it with struct inside struct.
I'm using Charts pod and the array chartMetricItemSets contains different readings with multiples days. ex: ["name": sugar, [["date": "Oct 10", "value": 120], ["date": "Oct 10", "value": 121], ["date": "Oct 11", "value": 120], ["date": "Oct 13", "value": 120], ["date": "Oct 13", "value": 121]]. The xAxisDates will be ["Oct 10", "Oct 11", "Oct 13"]
the x is the date and the y is for the same date sums
Goal: chartDataEntries should append [x:0(10/10), y:241], [x:1(10/11), y:120], [x:2(10/13), y: 241]
Thank you!
struct ChartData {
let name: String
let value: [ChartValue]
}
struct ChartValue {
let date: Date
let value: Double
}
var chartMetricItemSets = [[ChartData]]()
var xAxisDates = [String]() //date array
func getChartDataSets() {
for (index, chartHealthItemSet) in chartMetricItemSets.enumerated() {
let dataSourceSelected = dataSourcesSelected[index]
var chartDataEntries = [ChartDataEntry]() //array display on graph
var chartYaxis: Double = 0
var datePoint: Int = 0
var countChartYaxis = 0
var duplicateDateCount = 0
for (index, item) in chartHealthItemSet[0].value.enumerated() {
for (dateIndex, date) in xAxisDates.enumerated() {
if item.date.convertDateToString(dateFormat: .monthDay) == date {
chartYaxis += item.value
datePoint = dateIndex
chartYaxis += 1
break
}
//missing counting duplicateDateCount
if duplicateDateCount == countChartYaxis {
chartDataEntries.append(ChartDataEntry(x: Double(datePoint), y: chartYaxis.roundUpNDecimal(n: 2)))
}
}
print("chartYaxis: \(chartYaxis)")
print("chartDataEntries: \(chartDataEntries)")
}
chartDataEntries.sort(by: { $0.x < $1.x })
let set = LineChartDataSet(entries: chartDataEntries,
label: HEALTH_TABLE.allCases[dataSourceSelected].info.name)
set.mode = .linear // cubic line effect (smoother)
set.colors = [HEALTH_TABLE.allCases[dataSourceSelected].info.chartColor]
set.setCircleColor(.black)
set.lineWidth = 1.5
set.circleRadius = 3
set.drawCircleHoleEnabled = false
set.drawCirclesEnabled = true
set.drawHorizontalHighlightIndicatorEnabled = true
lineChartData?.addDataSet(set)
}
chartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: xAxisDates)
chartView.xAxis.granularity = 1
chartView.xAxis.axisMinLabels = 4
chartView.xAxis.axisMaxLabels = 6
DispatchQueue.main.async {
self.chartView.data = self.lineChartData
}
}
In Swift 4 a useful API was introduced to group arrays to dictionaries by an arbitrary predicate.
You can group chartHealthItemSet[0]
let grouped = Dictionary(grouping: chartHealthItemSet[0], by: {$0.date.convertDateToString(dateFormat: .monthDay)}
The result is ["10/10":[ChartData], "10/11":[ChartData], ...]
Sort the keys and assign them to xAxisDates
xAxisDates = grouped.keys.sorted()
To get the sum of the values get the array from the grouped dictionary for the date key, map it to the values and sum them up
for (index, date) in xAxisDates.enumerated() {
let sum = grouped[date]!.map{$0.value}.reduce{0.0, +}
chartDataEntries.append(ChartDataEntry(x: Double(index), y: sum.roundUpNDecimal(n: 2)))
}

How to sort rows of custom array by a value?

I have an array value that needs to be in this order
let daysOfWeekArray = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
and currently my array looks like this
let allData = [CustomObject(day:"Tuesday", startTime: "5:00"), CustomObject(day:"Sunday", startTime: "3:00")]
array.append(allData)
How do I get the array to sort by the day?
Sorry if this seems like an easy question, new to sorting arrays.
You can look up the index of the day in your reference array and sort by that index value
array.sort(by: {
(daysOfWeekArray.firstIndex(of: $0.day) ?? Int.max) < (daysOfWeekArray.firstIndex(of: $1.day) ?? Int.max)})
I use Int.max in case the day isn't found so that element gets sorted last.
On a side note, the array you are using for order can be gotten from the Calendar class from the property weekDaySymbols so the sorting could be done by using that property directly
let calendar = Calendar.current
array.sort(by: {
(calendar.weekdaySymbols.firstIndex(of: $0.day) ?? Int.max) < (calendar.weekdaySymbols.firstIndex(of: $1.day) ?? Int.max)})
Of course you need to be sure you use the right locale for your calendar.
Well maybe not the best solution, but looks like worked.
let daysOfWeekArray = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
struct CustomObject {
var day: String
var startTime: String
}
let allData = [CustomObject(day:"Tuesday", startTime: "5:00"), CustomObject(day:"Friday", startTime: "3:00"), CustomObject(day:"Sunday", startTime: "3:00"), CustomObject(day:"Monday", startTime: "3:00")]
let sorted = allData.sorted { (a, b) -> Bool in
let aIndex = daysOfWeekArray.firstIndex(of: a.day)!
let bIndex = daysOfWeekArray.firstIndex(of: b.day)!
if aIndex > bIndex {
return false
} else {
return true
}
}
print(sorted)
If I were you, I would change those strings to enum like that:
enum DayOfWeek {
case sunday
case monday
case tuesday
case wednesday
case thursday
case friday
case saturday
var title: String {
switch self {
case .sunday: return "Sunday"
case .monday: return "Monday"
case .tuesday: return "Tuesday"
case .wednesday: return "Wednesday"
case .thursday: return "Thursday"
case .friday: return "Friday"
case .saturday: return "Saturday"
}
}
var dayInWeek: Int {
switch self {
case .sunday: return 0
case .monday: return 1
case .tuesday: return 2
case .wednesday: return 3
case .thursday: return 4
case .friday: return 5
case .saturday: return 6
}
}
static var all: [DayOfWeek] {
return [.sunday, .monday, .tuesday, .wednesday, .thursday, .friday, .saturday]
}
}
struct CustomObject {
let day: DayOfWeek
let startTime: String
// ....
}
let daysOfWeekArray = DayOfWeek.all
let allData = [CustomObject(day:.tuesday, startTime: "5:00"), CustomObject(day:.sunday, startTime: "3:00")]
var array: [CustomObject] = []
array.append(contentsOf: allData)
array = array.sorted { $0.day.dayInWeek < $1.day.dayInWeek }
print(array.map{$0.day.title})

How can I compare two date string in array of Objects using NSPredicate?

I want to compare dates of each object using NSPredicate. If the Object has same dateCreated It will return an array of object which has same dates.
In the below Array of dictionary 0 index has the different date as compared to another one how can I get data like that.
Ex:
{
"Data": [
{
"id": "c9967156ad8945fba8cc482cd8aad900",
"description": "Hi",
"dateCreated": "2018-03-20T06:15:11.000+0000",
},
{
"id": "343e70818044457b884f7ad1907803fa",
"description": "The only ",
"dateCreated": "2018-03-16T17:22:50.000+0000",
},
{
"id": "dd542edfaa364e40ae0ef0562b6831be",
"description": "The new ",
"dateCreated": "2018-03-16T17:10:36.000+0000",
},
{
"id": "090f43c83e5b42039f70b133d031e715",
"description": "The new version ",
"dateCreated": "2018-03-16T17:08:07.000+0000",
},
{
"id": "b2ddb8fa990843a28f0670d2b88e3d01",
"description": "Add to the test",
"dateCreated": "2018-03-16T17:08:07.000+0000",
}
]
}
#Edit1:
I am converting dateCreated String object to Date and then I am using NSPredicate for desired data. Currently, I am trying with NSPredicate
#Edit2
Currently, I am not using NSPredicate. I am iterating each element of the array and compare its date
if let dateCur = dateCreated.dateFromISO8601 {
if let datePrev = dateCreatedPrev.dateFromISO8601 {
let curLocal = ISO8601.getStringDate(dateCur). // dd/MM/yyyy
let prevLocal = ISO8601.getStringDate(datePrev). // dd/MM/yyyy
if (curLocal.compare(prevLocal) != .orderedSame {
//diffrent
}else {
//same
}
}
}
I am using an extension for achieving it
extension Date {
func getFormatter() -> DateFormatter {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .gregorian)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
return formatter
}
var iso8601: String {
return Formatter.iso8601.string(from: self)
}
extension String {
var dateFromISO8601: Date? {
return Formatter.iso8601.date(from: self) // "Mar 22, 2017, 10:22 AM"
}
}
Can you please help me.
First of all there is no need to use NSPredicate in Swift (except the APIs which require NSPredicate).
The native filter method is more appropriate.
I recommend to decode the JSON
let jsonString = """
{
"Data": [
{"id": "c9967156ad8945fba8cc482cd8aad900", "description": "Hi", "dateCreated": "2018-03-20T06:15:11.000+0000"},
{"id": "343e70818044457b884f7ad1907803fa", "description": "The only ", "dateCreated": "2018-03-16T17:22:50.000+0000"},
{"id": "dd542edfaa364e40ae0ef0562b6831be", "description": "The new ", "dateCreated": "2018-03-16T17:10:36.000+0000"},
{"id": "090f43c83e5b42039f70b133d031e715", "description": "The new version ", "dateCreated": "2018-03-16T17:08:07.000+0000"},
{"id": "b2ddb8fa990843a28f0670d2b88e3d01", "description": "Add to the test", "dateCreated": "2018-03-16T17:08:07.000+0000"}
]
}
"""
into custom structs
struct Root : Decodable {
private enum CodingKeys : String, CodingKey { case items = "Data" }
let items : [Item]
}
struct Item : Decodable {
let id, description : String
let dateCreated : Date
}
The decoder uses a custom date formatter to decode the ISO8601 date properly
let data = Data(jsonString.utf8)
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
do {
let result = try decoder.decode(Root.self, from: data)
The array is in result.items. Now you can filter the array by a specific date. In this example the reference date is created hard-coded with DateComponents.
let components = DateComponents(timeZone: TimeZone(secondsFromGMT: 0), year:2018, month:03, day:16, hour:17, minute:8, second:7)
let date = Calendar.current.date(from: components)!
let filteredItems = result.items.filter { $0.dateCreated == date }
print(filteredItems)
} catch { print(error) }
If you want to find all matching records for a given date in the array use a loop
for item in result.items {
let filteredItems = result.items.filter { $0.dateCreated == item.dateCreated }
if filteredItems.count > 1 { print(filteredItems) }
}
You might look into Dictionary's initializer init(grouping:by:) which was introduced in Swift 4.
You should have your objects array. Then you can group them into [String:Object] where the key will be your Date in yyyy-MM-dd format as String.
Prerequisites: Your dateCreated property should be in Date format.
let groupedObjectBySameDate = Dictionary(grouping: objects) { (object) -> String in
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter.string(from: object.dateCreated)
}
An example:
As I don't know your data structure, I'm providing an easy example that can help you understand what's going on.
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ"
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}
let stringDates = ["2018-03-20T06:15:11.000+0000", "2018-03-16T17:22:50.000+0000", "2018-03-16T17:10:36.000+0000", "2018-03-16T17:08:07.000+0000", "2018-03-16T17:08:07.000+0000"]
let dates = stringDates.map { (each) -> Date in
return dateFormatter.date(from: each) ?? Date(timeIntervalSinceReferenceDate: 0)
}
let groupedObjectBySameDate = Dictionary(grouping: dates) { (date) -> String in
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter.string(from: date)
}
print(groupedObjectBySameDate)
// This will produce:
// [
// "2018-03-20": [2018-03-20 06:15:11 +0000],
// "2018-03-16": [2018-03-16 17:22:50 +0000, 2018-03-16 17:10:36 +0000, 2018-03-16 17:08:07 +0000, 2018-03-16 17:08:07 +0000]
// ]

is there a better way to align this column of numbers in a Swift textView?

I need to format a column of numbers in textView by displaying a column of fractional and decimal numbers and aligning them so the decimal point or forward slash characters appear in the same column like so ...
x 1/1
1 76.04900
x 193.15686
x 310.26471
4 5/4
x 503.42157
6 579.47057
x 696.57843
x 25/16
9 889.73529
x 1006.84314
11 1082.89214
This was achieved in Swift by inserting the appropriate number of leading whitespace characters and using left justification with a font where the column width is the same for each character.
To calculate the number of whitespaces, characters in the remainder or the dividend are excluded from the total number of characters in each number. This simple task involved subtracting .index(of: ) from .count and was complicated by the fact that .index(of:) returns an optional Array.Index? not an Int?
I suspect there might be a simpler way to achieve the same result and welcome any improvements to the Swift code below that might make life easier for whoever has to maintain it. The code was extracted from my project and has been tested in Playground.
Note. textMessage is a single string. It is currently displayed in the Debug area. It will eventually be displayed in a textView.
//: Playground
import UIKit
var tuningArray = ["1/1", "76.04900", "193.15686", "310.26471", "5/4", "503.42157", "579.47057", "696.57843", "25/16", "889.73529", "1006.84314", "1082.89214"]
var keyArray = ["x", "1", "x", "x", "4", "x", "6", "x", "x", "9", "x", "11"]
var characterCountArray = [Int]()
var scaleSize = Int()
var textMessage = String()
func formatTextMessage() {
var formattedTextArray = [String](repeating: "", count: scaleSize)
for i in 0..<scaleSize {
let element = tuningArray[i]
countNumericCharactersBeforeDotOrSlash (s: element)
}
let largestStringSize = characterCountArray.reduce(Int.min, { max($0, $1) })
for i in 0..<scaleSize {
let thisStringSize = characterCountArray[i]
let blanks = largestStringSize - thisStringSize
let filler = insertWhiteSpace(count: blanks)
formattedTextArray[i].append("\t" + keyArray[i] + "\t" + filler + tuningArray[i] + "\n")
textMessage += formattedTextArray[i]
}
print(textMessage)
}
func insertWhiteSpace(count: Int) -> String {
var filler = ""
for _ in 0..<count {
filler = filler + " "
}
return filler
}
func countNumericCharactersBeforeDotOrSlash (s: String) {
let fractionalNumber = findFractionalNumber(s: s)
let decimalNumber = findDecimalNumber(s: s)
var thisStringSize = 0
if !fractionalNumber && !decimalNumber {
print("Error: invalid number format")
}
if fractionalNumber == true {
let pitchStringArray = Array(s.characters)
let endIndex = pitchStringArray.count
if let slashIndex = pitchStringArray.index(of: "/") {
let dividendFiller = endIndex - slashIndex
thisStringSize = s.characters.count - dividendFiller
}
} else {
if decimalNumber == true {
let pitchStringArray = Array(s.characters)
let endIndex = pitchStringArray.count
if let dotIndex = pitchStringArray.index(of: ".") {
let remainderFiller = endIndex - dotIndex
thisStringSize = s.characters.count - remainderFiller
}
}
}
characterCountArray.append(thisStringSize)
}
func findDecimalNumber(s: String?) -> Bool {
if (s?.contains(".")) == true {
return true
}
return false
}
func findFractionalNumber(s: String?) -> Bool {
if (s?.contains("/")) == true {
return true
}
return false
}
scaleSize = tuningArray.count
formatTextMessage()
You could use attributed text with custom tab stops, like in this example:
import UIKit
import PlaygroundSupport
var tuningArray = ["1/1", "76.04900", "193.15686", "310.26471", "5/4", "503.42157", "579.47057", "696.57843", "25/16", "889.73529", "1006.84314", "1082.89214"]
var keyArray = ["x", "1", "x", "x", "4", "x", "6", "x", "x", "9", "x", "11"]
let scaleSize = tuningArray.count
var textMessage = ""
for i in 0..<scaleSize {
var textLine = "\t" + keyArray[i] + "\t" + tuningArray[i] + "\n"
textMessage += textLine
}
let paragraphStyle = NSMutableParagraphStyle()
let decimalTerminators:CharacterSet = [".", "/"]
let decimalTabOptions = [NSTabColumnTerminatorsAttributeName:decimalTerminators]
let decimalTabLocation = CGFloat(100) // TODO: Calculate...
var decimalTab = NSTextTab(textAlignment: .natural, location: decimalTabLocation, options: decimalTabOptions)
var leftTab = NSTextTab(textAlignment: .left, location: CGFloat(0.01), options: [:])
paragraphStyle.tabStops = [leftTab, decimalTab]
var a = NSAttributedString(string:textMessage,attributes: [NSParagraphStyleAttributeName: paragraphStyle])
let tv = UITextView(frame:CGRect(x:0, y:0, width:400, height:200))
tv.attributedText = a
PlaygroundPage.current.liveView = tv
The only drawback is, that you have to calculate the position of the decimal tab somehow; in the example I just use CGFloat(100) to get a nice value. But maybe you could simply live with a constant like this.
Greg,
Instead of using space to set columns use tab (\t) tree for times based on columns size you need.
That will help to manage code better way with very less and powerful coding.
Thanks

Resources