I just read this post about adding buttons with a loop programmatically.
I want to do the same thing, but do it with a name for each one, and animate them the same way. Is this a good idea, or should I just copy the same line of code for each button?
Further, I'd like to add a number of buttons (say four) from a list of names (greater than four) and pick them randomly. The catch is, I need them to be named properly and pull images associated with each name. Any ideas?
Thanks SO community!
As I see it, you should make a property list (.plist) with the array of buttons info - for each button there will be text to display, pic to display and action (method name or something).
You can generate four different random numbers in range [0, [buttonArray length]] and then generate your buttons in the loop for each selected number.
I guess, you need something like buttonFactory with method
-(UIButton*) makeButtonWithInfo(NSDictionary*)info
where name, picture adress, action etc. stored in info (you custom type buttonInfo if it is complicated).
Update:
Create new .plist in Xcode (resources/PropertyList) and fill it like this:
Read it in your code with
NSArray* buttonsArray = [NSArray arrayWithContentsOfFile:myPlist.plist];
There will be dictionaries with button info in this array.
Read in Xcode help about NSArray, NSDictionary and UIButton classes and implement your logic.
Related
I have a bunch of static objects (UILabel, buttons, views) in multiple Scenes. They are not connected to any IBOutlet. But I'd like to access them at appdelegate (or first VC), and change their properties before it is loaded.
Anyway to do this?
EDIT: Adding my intention:
I actually wanted to make a custom "multi-language" app. I want to be able to change language from within the app. I can get a list of all the objects by applying built in localization of storyboard (Main.strings is autogenerated). Then I disable localization again. Then from this autogenerated file, I want to be able to connect it to a json data based on language that I select.
Of course you can. For example, you can use tags of UIView. Just set tags in Storyboard. It's easy but not so good. Another way to do this is using Accessibilities. Enable and set for it in Storyboard.
And then you can access it by accessibilityIdentifier property.
I will post my choice of "solution". So what I did was make use of accessibilityIdentifier to set the "key" for the multilanguage phrase translation purpose.
And I make use of the UIView+Recursion class (you can find this simple class somewhere in SO), and basically iterate all the objects in a particular Scene and if the text matches, set the key in accessibilityIdentifier property (either in viewDidload or viewWillAppear or viewDidlayoutSubviews).
This way you can have language changes "on-the-fly" within the app, without restarting.
I am using iOS UITest for a Swift application. I use something like,
func testAllScreenNavigation() {
let app = XCUIApplication()
app.tabBars.buttons["Home"].tap()
app.navigationBars["Home"].buttons["More"].tap()
app.sheets.buttons["Cancel"].tap()
}
etc. to navigate some of the specific, tabs, buttons, etc. and switch to respective screens. But i want to navigate each and every screens of my Application (It can be BFS style navigation or DFS style navigation, no matter). Is there any way iOS provides so i can get all navigable elements and then explore deeper and deeper automatically for my App?
I also need to keep trace of which xcuoelement in a screen is already processed and which are not yet processed.
The only way I can think of is using Xcode UI test recorder feature.
While you are recording, navigate through all of your screens via the device/simulator and then the XCUIApplication() variable would be recorded with the appropriate references.
If the button/nav bar/any element has text on it, it will show up in the recorded code or else it will be referenced numerically.
Hope that helps.
Kind regards,
Mukund
I like your idea for getting all views and check whether the layouting and localization for example is fine.
I think you need to specify your criteria for "screens" and how they are accessed.
Basically, one could thing of the following structure
- UITabBarController
-- UISplitViewController
--- UINavigationController
---- UIViewController
----- UIBarButtonItems
----- UIView
----- UIButton
----- UISwitch
----- UITableViewCell
You could now go top down from the UITabBarController to the next controlling instance (might also skip one, e.g. SplitViewControllers on iPhones).
You can use the general property:
XCUIApplication().tabBars
Nevertheless that transition is the problem: How would you get from one ViewController to another and are they all position in the ViewController's View or do you have to loop the subviews of a view.
UIButton -> Touch Up Inside
UISwitch -> Value Changed
UITableViewCell -> DidSelectRowAtIndexPath
UIView -> UILongPressGestureRecognizer
This is how I would basically set it up:
For each UIViewController instance, get the related View (and perform the following call recursively).
Check all the subviews of a view.
For UIViews, go even further and check their subviews
For UIButtons, perform TouchUpInside
and so on.
Make sure to have a condition to stop going deeper, as UITableViews got a lot of subviews or your UIWebViews would of course be set up in a different way.
This way you should be able to navigate through a lot Views in your app hierarchy, but you will need some extensions for UIBarButtonItems, custom Gesture Recognizers and of course also for your "special" controls that might listen to value changes and perform a layout-change.
Accessing specific elements
In addition to the above approach where you simply get an array of elements of a specific type, you can access specific elements (e.g. those where you know they are of a very specific type with certain ValueChangeListeners or something)
To access a specific object in particular, like the TabBar example from above, you can use the accessibilityLabel like so. At first you need to declare the accessibilityLabel in your code or in the .xib-file/.storyboard:
// just to illustrate, so you get an idea:
self.tabBarController.isAccessibilityElement = true
self.tabBarController.accessibilityLabel = "tabBar"
And then do:
let tabBar = XCUIApplication().tabBars["tabBar"]
Here is Apple's documentation for setting these accessibilityLabels:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/iPhoneAccessibility/Making_Application_Accessible/Making_Application_Accessible.html
A great way to get the related identifier of an element would be to use the Accessibility Inspector from Apple:
https://developer.apple.com/library/content/technotes/TestingAccessibilityOfiOSApps/TestAccessibilityiniOSSimulatorwithAccessibilityInspector/TestAccessibilityiniOSSimulatorwithAccessibilityInspector.html
Accessing elements in general
To access elements in general, you need to make use of the XCUIElementType of these objects, here you will access the objects based on their classes.
E.g. you could call:
"tabBars", "navBars", "tables", "buttons", and so on from the elements in general.
Still you would be facing the issue with "special controls". As the Apple documentation lacks (imho) some detail about properties and attributes, I do recommend the docs here: https://blog.metova.com/guide-xcode-ui-test/ It provides a great overview of what is accessible and may help you getting some better understanding.
An overview of the available XCUIElementTypes can be found here. Basically, the elementType property is an enumerated value that represents the type of an element. XCUIElementType is a very large enumeration and some of its members do not apply to iOS applications (they apply to MacOS X apps). Some of the more commonly used values are:
Alert
Button
NavigationBar
TabBar
ToolBar
ActivityIndicator
SegmentedControl
Picker
Image
StaticText
TextField
DatePicker
TextView
WebView
https://developer.apple.com/reference/xctest/xcuielementtype?language=objc
So I'm new Objective C and I'm building my first app, although I'm stuck on a particular function for my character creation feature.
I have added 'back' & 'forward' buttons and these are meant to move between the selections that I have (e.g. shirt images).
I have 4 different choices for the shirt and was wondering how I would add these 4 images into an array that I would then be able to use with the 'back' and 'forward' buttons in order to navigate between the choices.
Any help would be appreciated.
Are you using Swift or Objective-C? I'll assume Objective-C. Ok. So you got it. Now you just got to implement it. You'll have to initialize a new array with the four images, arrayWithObjects: or initWithObjects:. You'll have to have a UIImageView drawn on the screen, added as a subview (UIView's addSubview:). Then you may want to keep a disposable "counter" private ivar (could be a property too if you wanted) that keeps track of the index of the current image being displayed (declared in your .m, .h is fine too). You could do this or you could have a dynamic getter method that returns the index based on the method objectAtIndex: on your array with your image view's image. When the user clicks forward or back the counter is updated (if it's an ivar and not a getter) and the image view's image is updated based on the new counter value (eg. self.imageView.image = imageArray[counter]) or something like that. You could disable the forward/back buttons if the counter value is on the edge of the array's count property. When the view is initially loaded you could load the image view's image based on the current counter value which would be 0 by default.
In this answer, I did not write the code for you. I showed you snippets of stuff and gave you different potential alternatives to write your program. You'll have to put it together yourself and choose what you're most comfortable with.
In Objective-C, there are two classes of objects: Primitives (like NSInteger, int, char) and everything else. Non primitives can be stored in NSArrays. NSArrays are immutable, meaning that you cannot change them once they have been created. However, there is a sub-class of NSArrays called NSMutableArray. You can add and delete objects to these and sort them if you want. So if you have four
UIImage objects, you could do something like this:
NSMutableArray* images = [NSMutableArray new];
[images addObject:image1];
[images addObject:image2];
[images addObject:image3];
[images addObject:image4];
....
The objects are added in order, so you can retrieve the second image (for example) like this:
UIImage* second = [images objectAtIndex:1];
(Indexing is zero-based)
I'm new to iOS and I'm busy with my first app. I have a question: In the code below is there any way to have my app auto generate a text field when a button is pressed without me having to set it up first?
For example in the code below would it be possible to get input from a text field and use it in place of "field1" to auto generate another textfield.
UITextField *field1 = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 30)];
field1.placeholder = #"Textbox 1";
field1.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:field1];
x = 10;
w = 100;
h = 30;
y = y + 60;
UITextField *field1 = [[UITextField alloc] initWithFrame:CGRectMake(x, y, w, h)];
field1.placeholder = #"Textbox 1";
field1.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:field1];
OK i see it was my code. In CGRectMake i replaced the values with variables which i incremented now with every button press and its more text fields, before it was adding on on top of the other thats why it seemed like it wasnt adding new text fields.
Now 2 questions on the above,
can i access the textfields individualy using this method.
Is this considered best practice or does it just use a lot of memory.
General Answer
Going by your question and comments, it looks like you want to keep adding an arbitrary number of fields and manage them as you go (keep them around so you can access and manipulate them). You'll still need to write code to create a field and stash it somewhere.
You can create a -createAndStoreFieldWithName: method that's called by your action. Such a method would use something similar to the code you wrote above but would store it somewhere. I'd recommend an 'NSMutableDictionary' that lives permanently in your controller, since dictionaries provide named access to their content.
This way, if you want to create a field called "Foo", your -createAndStoreFieldWithName: method would check to see if an entry named Foo already exists in the fieldsDictionary dictionary and, if not, would create the field (and add it to its superview, positioned wherever you wish) in code and call -[fieldsDictionary setObject:newlyCreatedField forKey:fieldName] to store it. That way, you can always get the field by name by asking -[fieldsDictionary objectForKey:desiredFieldName].
If you later want to remove them, use the same "get the field by name" approach to access the field, remove it from its superview, and remove it from the dictionary so it can be disposed of properly.
Of course if multiple fields with the same name can exist (ie, more than one Foo field), you'll need to add a layer of abstraction. In this case, you can use a unique identifier (like a UUID) the user never sees. The thing to figure out (which is hard to specify without more detail from you) is how you'll match the identifier to the proper field (given the possibility of multiple Foo fields, for example).
Also, you could just add the fields to the superview and loop through its -subviews array to locate fields by their label but this is anti-pattern to proper MVC design. Your controller object (the one creating the labels and adding them to other views) should be keeping track of these fields as I mentioned above, not the superview. This lets your controller be the intelligent mediator, since it knows what the fields are for and will use them (or possibly hand them off to some other controller) as it needs.
Situation-Specific Answer
BUT - consider whether individual UITextFields are the right way to go. Perhaps a UITableView with added rows is the better choice here? In this case your controller just decides there's one more row and kicks the table view to update. When asked about the cell, just give it your desired label from some labels container (maybe just a straight NSMutableArray of labels maintained by your controller - it matches array index to row index). MUCH simpler.
More detail in your question will get you more specific answers. Again, though, edit your original question - don't keep appending comments.
I assume you have a buttonPressed: method? Your question is not very clear, so if you'd like to clarify, I'm sure we can provide you with a better answer. Assuming you have a textField named inputField as a property of your view controller, you could do something like this:
- (void)buttonPressed:(id)sender {
NSString *currentText = self.inputField.text;
UITextField *newTextField = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 30)];
newTextField.placeholder = currentText;
newTextField.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:newTextField];
}
This would create a new UITextField upon button press with its placeholder text set to whatever was in your other UITextField. This is assuming you just want to do this once and that you only have a single new UITextField. If you can do this multiple times, but still just want a single UITextField updated with new text, newTextField should be a property, and you check if it is not nil and alloc init if it is. If it's not nil (it already exists), you need only update the text/placeholder property with the new text.
If every time the user enters text, you need a new UITextField in a new location, the solution would be a little different. Again, all of these answers (mine and others) are making assumptions about what you are trying to achieve.
UPDATE: Allow me to update this answer appropriately, as was previously pointed out. The long-and-short of it would be to use #JoshuaNozzi's approach, as it will achieve what you're trying to do. Just updating this for completeness.
note: This is an expansion (and clarification) of a question I asked yesterday.
I am conducting a research project where I want to record all of the user's touches in the iPhone app. After the experiment, I will be able to download the data and process it in either Excel or (more likely) Matlab and determine how many times they clicked on certain buttons, when they clicked certain buttons, etc. To do this, I would need to know:
a) When they touched
b) Where they touched
c) Which view they touched
The first two are easy, but the third I am having trouble with. I know I can do this to get the reference to the UIView that was touched:
CGPoint locationPoint = [[touches anyObject] locationInView:self];
UIView* viewYouWishToObtain = [self hitTest:locationPoint withEvent:event];
However, that will just give me a pointer to the view, not the name of the view that was touched. I could assign each view a tag, but then every time I create a new view I would need to remember to tag it (or, alternatively, log the address of each view when initialized and log it when the view is touched). Subclassing UIView and adding an automatic tag isn't really an option since I'm creating other UIButtons and UISliders and would need to subclass those also, which doesn't seem like a very good solution.
Does anyone know of a clean, easy way to do this?
For "Which view they touched", what information do you need?
Perhaps you could use a category to add a method to UIView. This method would generate a string containing information about the view. Such as:
its type e.g. UIButton etc.
its size and position
the title of the view, if it has one (e.g. the button title)
the parent view type and title
other stuff e.g. is the view enabled, what state it is in. anything you like.
For example: "Type:UIButton Title:"Back" Rect:{3,5,40,25}" or some such string.
This is very clean and gives you quite a lot of information to be going with.
You could add a category to UIView which would then be inherited by all UIView descended objects, although I'm not sure its any more efficient than tagging. Since a category can override methods then you could override init methods for automatic tagging I suppose.
http://macdevelopertips.com/objective-c/objective-c-categories.html
I'm not sure what you mean by the "name" of the view. If you mean the view name in Interface Builder, I don't believe it includes that in the instantiated objects. You could use the Tag attribute which is included, but that's just a number and not a name.