I am working on project that writes to google sheets. I am trying to Unmerge cells. This function works however it unmerges everything in the sheet. This is because it is not setting the .range value. When I print (as seen below) the "test" value all range values are shown accordingly however when I print the "request.unmergeCells?.range" it says nil. I am further confused as I use this exact code elsewhere for a merge command and it loads the values fine (see second snippet of code.)
I have tried for days to resolve this issue with no avail. Any thoughts?
func unmergecell1() {
let request = GTLRSheets_Request.init()
let test = GTLRSheets_GridRange.init()
rowstart = 4
rowend = 100
columnstart = 0
columnend = 100
test.startRowIndex = rowstart
test.endRowIndex = rowend
test.startColumnIndex = columnstart
test.endColumnIndex = columnend
request.unmergeCells?.range = test
request.unmergeCells = GTLRSheets_UnmergeCellsRequest.init()
print("=========unmerge==============")
print(test)
print(request.unmergeCells?.range)
let batchUpdate = GTLRSheets_BatchUpdateSpreadsheetRequest.init()
batchUpdate.requests = [request]
let createQuery = GTLRSheetsQuery_SpreadsheetsBatchUpdate.query(withObject: batchUpdate, spreadsheetId: spreadsheetId)
service.executeQuery(createQuery) { (ticket, result, NSError) in
}
}
func mergecell() {
let request = GTLRSheets_Request.init()
request.mergeCells = GTLRSheets_MergeCellsRequest.init()
let test = GTLRSheets_GridRange.init()
test.startRowIndex = rowstart
test.endRowIndex = rowend
test.startColumnIndex = columnstart
test.endColumnIndex = columnend
request.mergeCells?.range = test
request.mergeCells?.mergeType = kGTLRSheets_MergeCellsRequest_MergeType_MergeRows
let batchUpdate = GTLRSheets_BatchUpdateSpreadsheetRequest.init()
batchUpdate.requests = [request]
let createQuery = GTLRSheetsQuery_SpreadsheetsBatchUpdate.query(withObject: batchUpdate, spreadsheetId: spreadsheetId)
service.executeQuery(createQuery) { (ticket, result, NSError) in
}
}
I think you need to switch these 2 lines:
request.unmergeCells?.range = test
request.unmergeCells = GTLRSheets_UnmergeCellsRequest.init()
The 2nd line initates the unmerge request, and only after you initate it can you add the range to it. So by making it the other way round, as below, it should work.
request.unmergeCells = GTLRSheets_UnmergeCellsRequest.init()
request.unmergeCells?.range = test
Related
I am trying to set a value for "background color" as seen below but when I print the value after setting it, it simply shows nil? This makes absolutely no sense. Please help, I have exhausted all resources already.
Thank you.
let request = GTLRSheets_Request.init()
request.repeatCell = GTLRSheets_RepeatCellRequest.init()
let colbox = GTLRSheets_GridRange.init()
colbox.startRowIndex = rowstart
colbox.endRowIndex = rowend
colbox.startColumnIndex = columnstart
colbox.endColumnIndex = columnend
request.repeatCell?.range = colbox
let color = GTLRSheets_Color.init()
color.blue = 60
color.red = 47
color.green = 77
request.repeatCell?.cell?.userEnteredFormat?.backgroundColor = color
request.repeatCell?.cell?.userEnteredFormat?.textFormat?.bold = true
request.repeatCell?.cell?.userEnteredFormat?.horizontalAlignment = "CENTER"
request.repeatCell?.fields = "userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)"
print(request.repeatCell?.cell?.userEnteredFormat?.backgroundColor)
let batchUpdate = GTLRSheets_BatchUpdateSpreadsheetRequest.init()
batchUpdate.requests = [request]
let createQuery = GTLRSheetsQuery_SpreadsheetsBatchUpdate.query(withObject: batchUpdate, spreadsheetId: spreadsheetId)
service.executeQuery(createQuery) { (ticket, result, NSError) in
}
Issue:
You should initialize cell and userEnteredFormat.
Solution:
cell refers to an instance of GTLRSheets_CellData, and should be initialized the following way:
request.repeatCell?.cell = GTLRSheets_CellData.init()
userEnteredFormat refers to an instance of GTLRSheets_CellFormat:
request.repeatCell?.cell?.userEnteredFormat = GTLRSheets_CellFormat.init()
Reference:
GTLRSheets_CellData
GTLRSheets_CellFormat
Swift: Initialization
I am consuming an API that gives me the next page in the Header inside a field called Link. (For example Github does the same, so it isn't weird.Github Doc)
The service that I am consuming retrieve me the pagination data in the following way:
As we can see in the "Link" gives me the next page,
With $0.response?.allHeaderFields["Link"]: I get </api/games?page=1&size=20>; rel="next",</api/games?page=25&size=20>; rel="last",</api/games?page=0&size=20>; rel="first".
I have found the following code to read the page, but it is very dirty... And I would like if anyone has dealt with the same problem or if there is a standard way of face with it. (I have also searched if alamofire supports any kind of feature for this but I haven't found it)
// MARK: - Pagination
private func getNextPageFromHeaders(response: NSHTTPURLResponse?) -> String? {
if let linkHeader = response?.allHeaderFields["Link"] as? String {
/* looks like:
<https://api.github.com/user/20267/gists?page=2>; rel="next", <https://api.github.com/user/20267/gists?page=6>; rel="last"
*/
// so split on "," the on ";"
let components = linkHeader.characters.split {$0 == ","}.map { String($0) }
// now we have 2 lines like '<https://api.github.com/user/20267/gists?page=2>; rel="next"'
// So let's get the URL out of there:
for item in components {
// see if it's "next"
let rangeOfNext = item.rangeOfString("rel=\"next\"", options: [])
if rangeOfNext != nil {
let rangeOfPaddedURL = item.rangeOfString("<(.*)>;", options: .RegularExpressionSearch)
if let range = rangeOfPaddedURL {
let nextURL = item.substringWithRange(range)
// strip off the < and >;
let startIndex = nextURL.startIndex.advancedBy(1) //advance as much as you like
let endIndex = nextURL.endIndex.advancedBy(-2)
let urlRange = startIndex..<endIndex
return nextURL.substringWithRange(urlRange)
}
}
}
}
return nil
}
I think that the forEach() could have a better solution, but here is what I got:
let linkHeader = "</api/games?page=1&size=20>; rel=\"next\",</api/games?page=25&size=20>; rel=\"last\",</api/games?page=0&size=20>; rel=\"first\""
let links = linkHeader.components(separatedBy: ",")
var dictionary: [String: String] = [:]
links.forEach({
let components = $0.components(separatedBy:"; ")
let cleanPath = components[0].trimmingCharacters(in: CharacterSet(charactersIn: "<>"))
dictionary[components[1]] = cleanPath
})
if let nextPagePath = dictionary["rel=\"next\""] {
print("nextPagePath: \(nextPagePath)")
}
//Bonus
if let lastPagePath = dictionary["rel=\"last\""] {
print("lastPagePath: \(lastPagePath)")
}
if let firstPagePath = dictionary["rel=\"first\""] {
print("firstPagePath: \(firstPagePath)")
}
Console output:
$> nextPagePath: /api/games?page=1&size=20
$> lastPagePath: /api/games?page=25&size=20
$> firstPagePath: /api/games?page=0&size=20
I used components(separatedBy:) instead of split() to avoid the String() conversion at the end.
I created a Dictionary for the values to hold and removed the < and > with a trim.
When I have multiple points in an array for a line on a line graph, everything shows perfectly.
But when there is only one point, the dot does not show. I dont know why?
the delegate is being set elsewhere, but this doesnt seem to be the issue.
The below examples shows Test 2 and Test exercise. The first image is where each has one value, the second they each have 2.
heres my code
func startChart(){
chart.dragEnabled = true
chart.legend.form = .circle
chart.drawGridBackgroundEnabled = false
let xaxis = chart.xAxis
xaxis.valueFormatter = axisFormatDelegate
xaxis.labelCount = dataSets.count
xaxis.labelPosition = .bottom
xaxis.granularityEnabled = true
xaxis.granularity = 1.0
xaxis.avoidFirstLastClippingEnabled = true
xaxis.forceLabelsEnabled = true
let rightAxis = chart.rightAxis
rightAxis.enabled = false
rightAxis.axisMinimum = 0
let leftAxis = chart.leftAxis
leftAxis.drawGridLinesEnabled = true
leftAxis.axisMinimum = 0
let chartData = LineChartData(dataSets: dataSets)
chart.data = chartData
}
If I add
chart.setVisibleXRangeMinimum(myMinDate)
the value will show correctly. however it squashes the value to the left and overlaps 2 x value dates
The only way I could get around this was to add an additional invisible line.
I created a clear line that started the day before and ended the day after my single values.
As long as there is a line on the chart that goes from one point to another, the other single values show.
var singleValue = false
for i in 0...(dataSets.count - 1) {
if dataSets[i].values.count > 1{
singleValue = true
}
}
var data = dataSets
if singleValue == false {
let minNS = Calendar.current.date(byAdding: .day, value: -1, to: minNSDate as! Date)
let maxNS = Calendar.current.date(byAdding: .day, value: 1, to: maxNSDate as! Date)
var dataEntries: [ChartDataEntry] = []
let dataEntry1 = ChartDataEntry(x:Double(String(format: "%.2f",Double((minNS?.timeIntervalSince1970)!)))!,y:00.00)
let dataEntry2 = ChartDataEntry(x:Double(String(format: "%.2f",Double((maxNS?.timeIntervalSince1970)!)))!,y:00.00)
dataEntries.append(dataEntry1)
dataEntries.append(dataEntry2)
let set = LineChartDataSet(values: dataEntries, label: "")
set.setCircleColor(UIColor.clear)
set.circleHoleColor = UIColor.clear
set.setColor(UIColor.white, alpha: 0.0)
set.drawValuesEnabled = false
data.append(set)
}
chart.chartDescription?.text = ""
let chartData = LineChartData(dataSets: data)
chart.data = chartData
I think I found a better solution. Single point is not enough to draw a line (you need at least two points) so LineChartView can't render your data. You can fix that by replace LineChartView with CombinedChartView. CombinedChartView give a possibility to mix different types of data on one chart. You can check how many data entires do you have and decide which type of DataSet will be proper.
Code example:
if dataEntry.count == 1 {
let scatterDataSet = ScatterChartDataSet(values: dataEntry, label: title)
scatterDataSet.setColor(UIColor.pmd_darkBlue)
scatterDataSet.setScatterShape(.circle)
scatterDataSet.drawValuesEnabled = false
combinedChartData.scatterData = ScatterChartData(dataSets: [scatterDataSet])
}
else {
let lineDataSet = LineChartDataSet(values: dataEntry, label: title)
lineDataSet.setColor(UIColor.pmd_darkBlue)
lineDataSet.lineWidth = 3.0
lineDataSet.drawCirclesEnabled = false
lineDataSet.drawValuesEnabled = false
combinedChartData.lineData = LineChartData(dataSets: [lineDataSet])
}
combinedChart.data = combinedChartData
You can also combine two and more types DataSets in one chart.
Important
Don't forget to add this line:
combinedChart.drawOrder = [DrawOrder.line.rawValue, DrawOrder.scatter.rawValue]
You must to write types of data types you use otherwise data will not render.
I'm using an API to get weather condition and the retrieved dict is
dict = {
base = stations;
clouds = {
all = 92;
};
cod = 200;
coord = {
lat = "31.23";
lon = "121.47";
};
dt = 1476853699;
id = 1796231;
main = {
"grnd_level" = "1028.63";
humidity = 93;
pressure = "1028.63";
"sea_level" = "1029.5";
temp = "73.38";
"temp_max" = "73.38";
"temp_min" = "73.38";
};
name = "Shanghai Shi";
rain = {
3h = "0.665";
};
sys = {
country = CN;
message = "0.0125";
sunrise = 1476827992;
sunset = 1476868662;
};
weather = (
{
description = "light rain";
icon = 10d;
id = 500;
main = Rain;
}
);
wind = {
deg = "84.50239999999999";
speed = "5.97";
};
}
If I want the value of humidity, I just use
let humidityValue = dict["main"]["humidity"] and it works.
But the problem is I also want to get the value of description in weather
when I used let dscptValue = dict["weather"]["description"]
it retrieved nil.
How's that? and I notice there are two brackets around weather .I'm not sure whether it is the same with the statement without brackets.
weather = (
{
description = "light rain";
icon = 10d;
id = 500;
main = Rain;
}
);
How to get the value of description?
weather keys contains Array of Dictionary not directly Dictionary, so you need to access the first object of it.
if let weather = dict["weather"] as? [[String: AnyObject]], let weatherDict = weather.first {
let dscptValue = weatherDict["description"]
}
Note: I have used optional wrapping with if let for preventing crash with forced wrapping.
Weather is an array of dictionaries.
dict["weather"][0]["description"]
may give you the expected result.
I'm writing ID3 tags to a file using AVMetaDataItem
var soundFileMetadata = [AVMetadataItem]()
soundFileMetadata.append(createMetadata(AVMetadataiTunesMetadataKeyArtist, "MyArtist")!)
soundFileMetadata.append(createMetadata(AVMetadataiTunesMetadataKeySongName, "MySong")!)
soundFileMetadata.append(createMetadata(AVMetadataiTunesMetadataKeyAlbum, "MyAlbum")!)
soundFileMetadata.append(createMetadata(AVMetadataiTunesMetadataKeyUserGenre, "MyGenre")!)
soundFileMetadata.append(createMetadata(AVMetadataiTunesMetadataKeyComposer, "MyComposer")!)
Here is the createMetadata convenience method:
func createMetadata(tagKey: String, _ tagValue: AnyObject?,
keySpace:String = AVMetadataKeySpaceiTunes) -> AVMutableMetadataItem? {
if let tagValue = tagValue {
let tag = AVMutableMetadataItem()
tag.keySpace = keySpace
tag.key = tagKey
tag.value = (tagValue as? String) ?? (tagValue as? Int)
return tag
}
return nil
}
I then tried to write also the year tag, with no success:
let comps = NSDateComponents()
comps.year = 2010;
let yearTag = AVMutableMetadataItem()
yearTag.keySpace = AVMetadataKeySpaceID3
yearTag.key = AVMetadataID3MetadataKeyYear
yearTag.value = NSCalendar.currentCalendar().dateFromComponents(comps)
soundFileMetadata.append(yearTag)
In this case I get this error:
FigMetadataCreateConverter signalled err=-12482 (kFigMetadataConverterError_UnsupportedFormat) (Unsupported format conversion) at /SourceCache/CoreMedia/CoreMedia-1562.238/Prototypes/Metadata/Converters/FigMetadataConverterCommon.c line 118
Note that this is a simple error printed in console, not an exception!
Also writing it as a String, as an Int o even a Float, leads me to the same error.
Same is for Track/Disc count, Track/Disc number tags.
First question is: how to write them?
I also have another question.
Currently I've an AVAudioRecorder, I found no way to write tags directly to the output file of the recorder, so I commit the recorder file, open it with AVURLAsset and re-export it with AVAssetExportSession:
self.recorder.stop()
let urlAsset = AVURLAsset(URL: srcSoundFileURL)
let assetExportSession: AVAssetExportSession! = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetPassthrough)
assetExportSession.outputFileType = AVFileTypeAppleM4A
assetExportSession.outputURL = tmpSoundFileURL
assetExportSession.metadata = soundFileMetadata
assetExportSession.exportAsynchronouslyWithCompletionHandler({
....
})
Second question is: is there any way to avoid this double-step action?
I've managed to add the year tag with your code with a few modifications:
let yearTag = AVMutableMetadataItem()
yearTag.keySpace = AVMetadataKeySpaceiTunes
yearTag.key = AVMetadataiTunesMetadataKeyReleaseDate
yearTag.value = "2123"
I couldn't make it work with the ID3 keys so I thought this could be the problem, and indeed it works with these iTunes keys. Also, the value has to be a String (or NSString), not a date object.