How to "list" text in columns in iOS? - ios

Basically, I wish to list some text like so, in a column view:
Item Colour Quantity
Glasses Black 3
It's not for user-input, more towards the user reading it. I know this can be accomplished via the use of labels, however, this does tend to get tedious. I have the text written up in a word document, tried to copy and paste it into a UITextView and mess around with the attributes, I get 99% close however the last "column" seems to always give me an issue.
Can anyone potentially shed some light onto how I can do this with ease?
P.S- I will probably have 10 "lists" each different.

You have basically three good possibilities.
1) You give it a try using labels
2) You take a UICollectionView or
3) some UITableViews side by side
I had the same problem and I decided to go with option 1)
Create a method which needs a 2d String array as input.
Determine the ScreenSize of the device.
func createRows(textName: [[String]])
{
dispatch_async(dispatch_get_main_queue())
{
if self.columnArrayActive || self.columnArray1.isEmpty
{
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
self.ScrollViewMain.contentSize.height = CGFloat(30 * textName.count))
Now create two for loops to iterate through the array.
Create the labels and modify them.
for i1 in 0..<textName.count
{
var columnArray = [UILabel]()
for i2 in 0..<textName[i1].count
{
columnArray.append((UILabel(frame: CGRectMake(CGFloat((Int(screenWidth) / 9) * i2 - Int(screenWidth / 128)), (CGFloat(30 * i1) - modifyY), CGFloat(Int(screenWidth) / 8), 30))))
columnArray[i2].text = textName[i1][i2]
columnArray[i2].textAlignment = NSTextAlignment.Center
I have additionally structured the rows with two different colors for a better reading experience.
if i1 == 0
{
columnArray[i2].backgroundColor = UIColor(red: CGFloat(1), green: CGFloat(0.35), blue: CGFloat(0.35), alpha: CGFloat(1))
} else {
if self.rowSwitcher
{ columnArray[i2].backgroundColor = settings.vari.color1 as? UIColor}
else
{ columnArray[i2].backgroundColor = settings.vari.color2 as? UIColor}
}
self.scrollViewMain.addSubview(columnArray[i2])
}
if i1 != 0
{
if self.rowSwitcher
{ self.rowSwitcher = false }
else
{ self.rowSwitcher = true }
}
self.columnArray1.append(columnArray)
}
self.columnArrayActive = false
This part of the code is used when you already have created your list but you need to update the values of the labels.
} else {
for i3 in 0..<textName.count
{
for i4 in 0..<textName[i3].count
{
self.columnArray1[i3][i4].text = textName[i3][i4]
}
}
}
}
}
You need to define some global or class variables to get it work:
1)columnArray1 : [[UILabel]]
2)columnArrayActive : Bool
3)scrollViewMain : UIScrollView!
You create in the Interface Builder a UIScrollView filling you screen.
Then you create constraints.
Afterwards you create a reference to your class and in viewDidLoad you add:
scrollViewMain = self.delegate
This means that your class needs of course to inherit from UIScrollViewDelegate!
The result looks like this:

Don't forget you can draw text into a graphics context. For read-only text, this is definitely an option I would consider. Another option which might work for you is text containers in Text Kit.

Related

Avoid redundant dereferencing in iOS

Suppose I want to change size of an uiView: UIView to w,h. I can do it like that:
uiView.frame.size.width = w
uiView.frame.size.height = h
In another system I can avoid replication of dereferencing (which means waste of both size and performance) by keeping a reference in a variable (using Swift syntax):
let ref = uiView.frame.size
ref.width = v
ref.height = h
This however doesn't work in iOS, where CGSize is a structure and therefore is copied when assigned to another value.
Is there a way to avoid redundant dereferencing (something like with(uiView.frame.size){...} available in some languages)
I don't think there is a way to do it exactly because the frame is a value-copied structure. You could set the frame directly as Reiner Melian suggests, but to me that seems even longer and uses dereferencing at least the same amount of time as your approach.
There is a way how to make it simpler this using extensions, but behind the scenes it will again be using dereferencing:
extension UIView {
var width: CGFloat {
get {
return self.frame.size.width
}
set {
self.frame.size.width = newValue
}
}
var height: CGFloat {
get {
return self.frame.size.height
}
set {
self.frame.size.height = newValue
}
}
}
And then you could use:
uiView.width = w
uiView.height = h
on any UIView instance.
This is even simpler:
uiView.frame.size = CGSize(width: w, height: h)
As I understand it, RHS is a temporary value released as soon as the content has been copied to frame structure.

Change background of google slides shape to red

I am trying to find the answer in Google Slides API references for how to set the background color of a shape I have in my Google Slide. I have given it the title (using Alt Text feature) "rectangle1", so my intention is to write the code along the lines of "if shape's property "title" == "rectangle1", then set background color to red."
I can't see a single reference to "SetBackgroundFill" or SetBackgroundColor, or anything of that sort.
Is it possible?
This is another possible answer, using a so-called "container bound script", which is only accessible through the specific Slide's Tools/Script Editor menu (no other way, or else it won't work).
I found that this "container bound script" approach gives me more power over my slide, and it avoids these expensive calls to "batchUpdate", when using "stand alone" scripts as in my other "self-answer".
So, in a way, I recommend it to myself, but, perhaps, to someone else, my other approach would be a better choice.
For one thing, this approach has a much faster response time.
var hex_color = '#54BdeF';
function test1() {
var selection = SlidesApp.getActivePresentation().getSelection();
var currentPage = selection.getCurrentPage();
var selectionType = selection.getSelectionType();
var shapes = currentPage.getShapes();
for (i=0; i < shapes.length; i++) {
if (shapes[i].getTitle() == 'rectangle1') {
shape_fill = shapes[i].getFill();
shape_fill.setSolidFill(hex_color);
}
}
}
Again, as before, I would welcome any comments and suggestions.
To set background color, you need Element Operations.
The Slides API allows you to create and edit a variety of page
elements, including text boxes, images, tables, basic shapes, lines,
and embedded videos. The examples on this page show some common page
element operations that can be achieved with the API.
Following the steps specified here will do the changes in your specified shape or element. Check the example.
Well, here is my solution. If someone sees a way to improve it, I am all ears, but so far, it appears to work for me glitch-free.
First, I find the shape I am after using the following logic:
function ChangeColorMain()
{
ChangeShapeBackgroundColor('title', 'rectangle1', color_to_repl_r, color_to_repl_g, color_to_repl_b, alpha_value );
}
function ChangeShapeBackgroundColor(shape_property_name, shape_property_value, color_to_set_r, color_to_set_g, color_to_set_b) {
Logger.log( 'ChangeShapeBackgroundColor(shape_property_name=%s, shape_property_value=%s, color_to_set_r=%s, color_to_set_g=%s, color_to_set_b=%s) ',
shape_property_name, shape_property_value, color_to_set_r, color_to_set_g, color_to_set_b);
var presentation = Slides.Presentations.get(presentationId);
var slides = presentation.slides;
Logger.log('The presentation contains %s slides:', slides.length);
for (i = 0; i < slides.length; i++) {
for (j = 0; j < slides[i].pageElements.length; j++ ) {
if (shape_property_name == 'title' && shape_property_value == slides[i].pageElements[j].title) {
Logger.log('Found it');
//slides[i].pageElements[j].shape.shapeProperties.shapeBackgroundFill.solidFill.color.rgbColor.red = color_to_set_r;
SubmitRequest(slides[i].pageElements[j].objectId, color_to_set_r, color_to_set_g, color_to_set_b, alpha_value);
}
} //end of for that iterates through every element
}
}
So, you'll notice that I start my process by calling the function "ChangeColorMain" which also gets my global variables color_to_repl_r... which are defined in a different file of my google script project, but that's not important.
Once inside the ChangeShapeBackgroundColor(), I iterate through all "PageElements" on my slide (see the relevant for loops) and use if statements to check if I got to the shape I am looking for. Finally, once I have located it, I call the all important function SubmitRequest(), which is "expensive". You can't make too many calls in one day, or else Google blocks this function until the day ends. But not a problem if you are making less than 500 calls per day (this number might be wrong/might change).
Here are the details of "SubmitRequest()" which I was able to create by finally figuring out how to make sense of this reference page:
https://developers.google.com/slides/reference/rest/v1/presentations/request#UpdateShapePropertiesRequest
function SubmitRequest(shape_id, r, g, b, a) {
var rgb_color = {
red: r,
green: g,
blue: b
};
var opaque_color = {
rgbColor: rgb_color
};
var solid_fill = {
color: opaque_color,
alpha: a
};
var background_fill = {
solidFill: solid_fill
};
var shape_properties = {
shapeBackgroundFill: background_fill
};
var update_request = {
objectId: shape_id,
shapeProperties: shape_properties,
fields: "shapeBackgroundFill.solidFill.color"
};
var requests = [{
updateShapeProperties: update_request
}];
// Execute the request.
var batch_update_return = Slides.Presentations.batchUpdate({
requests: requests
}, presentationId);
Logger.log(
'This is what you get from Google after submitting batchUpdate request:\n%s', batch_update_return);
}

Matching two arrays of images by color with tags

I have two UIImageView arrays which I fill up with 18 blocks and circles
var myBlocks = [UIImageView]()
var myCircles = [UIImageView]()
So after I add my circles to the screen in a nice fashion, then my blocks overtop of them, I call a function to set the tags of the circles and block to match, if the colors match. Meaning if the block is bright red, I want to find the circle that is bright red, and tag them both 0, and so on. This line below where I set the tag is throwing the error:
Cannot subscript a value of type '[UIImageView]' with an index of type 'UIImageView'
func setTags() {
for x in myBlocks {
for y in myCircles {
if x.tintColor == y.tintColor {
myBlocks[x].tag = y //Error here
}
}
}
}
Is there an easier way to do this? The reason I don't tag both upon creation is because the circles are created using an array that is shuffled, being that I don't want the same colored circle and the same color block on top of each other when the game loads in.
Edit: I changed it to x.tag = y.tag and it seems better. However, I'm doing two print outs now when I tap one of my square blocks.
let objectDragging = recognizer.view?.tag
print(objectDragging)
//and
print("the tag of the object your touching is \(myBlocks[objectDragging!])")
and the log I'm getting during use is
Optional(13)
the tag of the object your touching is <UIImageView: 0x7f8d4ba103a0;
frame = (263 180; 100 100); opaque = NO; tintColor =
UIExtendedSRGBColorSpace 0.8 0.3 0.3 1; tag = 11; gestureRecognizers = <NSArray: 0x60000005fec0>; layer = <CALayer: 0x600000220580>>
So one is saying the block is tagged 13, and one is saying 11. The 13 is what it says when I print out myBlocks[count].tag, I just don't know where the 11 is coming from in the myBlocks[objectDragging] statement.
Edit 2: Is it because (myBlocks[objectDragging!]) is referencing a different block maybe?
You have two issues. First, myBlocks[x] will raise an error because you're looping through elements, not indices. Second, x.tag = y raises an error because the property tag is an Int and you're trying to assign a UIImageView.
for x in myBlocks {
for y in myCircles {
if x.tintColor == y.tintColor {
x.tag = y.tag //fix here
}
}
}
EDIT: Alternatively, if you want to loop through the indices:
for x in 0..<myBlocks.count {
for y in 0..<myCircles.count {
if myBlocks[x].tintColor == myCircles[y].tintColor {
myBlocks[x].tag = myCircles[y].tag
}
}
}
Finally, if you want the indices and the elements, you can do:
for (x, block) in myBlocks.enumerated() {
for (y, circle) in myCircles.enumerated() {
if block.tintColor == circle.tintColor {
myBlocks[x].tag = myCircles[y].tag
}
}
}
This line:
myBlocks[x].tag = y //Error here
y is an element of myCircles. It is not a tag.
Change that line to:
x.tag = y.tag

UILabels within an array not showing up on ViewController

So I'm trying to make a grid/table show up in my app, and to do this I created an array of UILabels with:
var rows = 2
var columns = 2
var grid = [[UILabel]](count: rows, repeatedValue: [UILabel](count: columns, repeatedValue: UILabel()))
then I created each UILabel with:
for i in 0 ..< grid.count
{
for j in 0 ..< grid[0].count
{
grid[i][j].frame = CGRectMake(x + CGFloat(j*Int(gridSpace)), y + CGFloat(i*Int(gridSpace)), gridSpace, gridSpace)
grid[i][j].backgroundColor = UIColor.redColor()
grid[i][j].hidden = false
grid[i][j].textColor = UIColor.whiteColor()
grid[i][j].textAlignment = NSTextAlignment.Center
grid[i][j].text = "label: [\(i)][\(j)]"
self.view.addSubview(grid[i][j])
print("grid[\(i)][\(j)]X = \(grid[i][j].frame.origin.x) grid[\(i)][\(j)]Y = \(grid[i][j].frame.origin.y)")
}
}
What happens is actually pretty interesting. The code compiles and everything, but only one of the labels shows up. The dimensions and everything about the label are perfect, but only one of them shows up.
The label that shows up is always the grid[rows-1][columns-1] label. But when I print the x and y coordinates of the rest of the labels that are supposed to be in the grid array, all of the coordinates are exactly where they are supposed to be on the viewcontroller it's just that the labels don't show up. when I changed the for loop to
for i in 0 ..< grid.count-1
{
for j in 0 ..< grid[0].count-1
{
still only the last label (the grid[rows-1][columns-1] label) shows up yet the coordinates are exactly where they should be. Another weird thing is that if I change the line
grid[i][j].text = "test label: [\(i)][\(j)]"
to
if(j != grid[0].count-1)
{
grid[i][j].text = "test label: [\(i)][\(j)]"
}
then the label that shows up still has the coordinates of the grid[rows-1][columns-1] but has the labeltext of grid[rows-1][columns-2]
Can anyone fix my problem for me or explain whats going on?
It's because of the way that you have initialized your grid with repeated values. If the repeated value is of reference type, the array will actually create only one item and point all indexes to that item, so in your for loops you're changing the frame for the one and only UILabel over and over again. thats why you only see the last one on your screen.
To create the array you want replace this:
var grid = [[UILabel]](count: rows, repeatedValue: [UILabel](count: columns, repeatedValue: UILabel()))
with
var grid = [[UILabel]]()
for row in 0..<rows {
grid.append([UILabel]())
for column in 0..<columns{
grid[row].append(UILabel())
}
}
or
var grid = [[UILabel]]((0..<rows).map { _ in
[UILabel]((0..<columns).map { _ in
UILabel()
})
})

How can I prevent orphans in a label in swift?

I have a label that can have one or two lines. If it has two lines, I want the second line to have at least two (or maybe three) words, never just one. Any ideas about how I can accomplish that using swift?
Thanks in advance!
Daniel
Edit: I edited out my silly first thoughts that didn't really help.
Ok, after looking around a lot I came up with what I think is the best solution.
I wrote this function:
func numberOfLinesInLabelForText(text: String) -> Int {
let attributes = [NSFontAttributeName : UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)]
let screenSize: CGRect = UIScreen.mainScreen().bounds
let labelSize = text!.boundingRectWithSize(CGSizeMake((screenSize.width - 30), CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: attributes, context: nil)
let lines = floor(CGFloat(labelSize.height) / bookTitleLabel.font.lineHeight)
return Int(lines)
}
You put in the string that will be displayed in the label and it gives you how many lines the label will have. I'm using dynamic type and the Headline style for this particular label, hence the preferredFontForTextStyle(UIFontTextStyleHeadline) part, but you can change that to the font and size your label uses.
Then I use (screenSize.width - 30) for my label's width because it's width is not fixed, so I'm using the screen size minus leading and trailing. This is probably not the most elegant solution, I'm open to better suggestions.
The rest is pretty straightforward.
After I have the number of lines I can do this:
func splittedString(text: String) -> String {
if numberOfLinesInLabel(text) == 2 {
var chars = Array(text.characters)
var i = chars.count / 2
var x = chars.count / 2
while chars[i] != " " && chars[x] != " " {
i--
x++
}
if chars[i] == " " {
chars.insert("\n", atIndex: i+1)
} else {
chars.insert("\n", atIndex: x+1)
}
return String(chars)
}
}
Instead of just avoiding orphans I decided to split the string in two at the breaking point nearest to its half, so that's what this last function does, but it wouldn't be hard to tweak it to suit your needs.
And there you have it!

Resources