I have just started with iOS development in objective C and I'm finding it difficult to get my head around UIView objects.
Here is what I'm doing:
In an existing project, I'm adding a new UIViewController.
In a storyboard I'm dragging and dropping UI elements.
I added some constraints into each of the UI elements (just playing around, there is no specific constraint as such, this could be anything).
Dragging each of these UIElement to ViewController.h to create a handler.
In ViewController.m file I'm trying to first synthesize all those objects and then further play around with some logics (again nothing very specific).
In ViewDidLoad I'm try to perform loading my UI elements.
During runtime when I trigger my custom UIViewController, then I could see from debugger that it actually comes into controller's ViewDidLoad section.
Problem:
During runtime, all the UI elements have nil assign to them. I don't understand this that why is synthesize not allocating them any memory. The execution just goes over my logics, but does nothing. I don't see anything displayed in my screen.
Question:
Why is this so? Why even after setting my UI constraints in storyboard and synthesizing each UI handler objects, I was not able to allocate memory to my UI objects?
Related
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
I'm having trouble understanding the idea of objects. From what I've read, they're instances of a class. When learning swift, they're quite easy to understand. Simply create a class and create an instance of it, and from there, you can modify it's properties and call its methods:
class ExampleClass {
let ExampleProperty = "rabbit"
}
let exampleInstance = ExampleClass()
But I don't see how that translates when using iOS, since I haven't seen any objects being created explicitly yet:
var example = Wss()
So my questions are:
Are things like buttons, labels, and sliders objects?
-If so, where's the "code" behind them? Why do buttons, labels, etc. display even before they're connected through outlets and actions to the View Controller? Is there a hidden "var thisButton = ThisViewController()" embedded into each of those sliders and buttons?
If my assumptions are wrong, can someone explain to me how objects work?
"Is there a hidden "var thisButton = ThisViewController()" embedded into each of those sliders and buttons?"
No, and this is exactly where interface builder excels. Much of Xcode's modern Interface Builder comes from NeXTSTEP. When you drag out a new UI component like NSButton and place it on your story board, Xcode is instantiating a new object of the NSButton class for you. When you save your file, Xcode serializes all the objects of your story board into a .nib file. At the time when this was invented, it was quite revolutionary, all made possible because of the dynamism of Objective C. It made GUI programming much simpler and dynamic. Every object in your story board is aware of its class. For example, when you instantiate a new NSButton, you can open the inspector and see for yourself that its class is NSButton. When you add custom views to your application, they keep track of their class in the same way. Whenever a nib file is loaded, these views are instantiated from their classes. You might have noticed that you never override the initializer of your views. Instead, you override methods like awakeFromNib. This is because there's a lot of behind the scenes work being done for you, from the time the object is first instantiated, to the time. During this time IBOutlets and IBActions are bound for you.
Competitors tried to make similar interface building applications, but they ultimately resorted to doing code generation behind the scenes. In these systems, when you saved your interface file, the program would generate a source file that contains code that instructs how to instantiate these objects anew whenever the interface is loaded. However, it proved significantly more complex a task then just serializing the objects, so these systems were error prone, and significantly harder to debug (because you'd be trying to debug machine generated source files).
Answering your questions:
Yes. Your objects are just being created from a NIB, or Storyboard. So the NIB, or Storyboard, will create those visual (UI) elements for you, which you can then be accessed via the IBOutlets
Your assumptions, are not completely wrong as in, there is in fact something allocating those objects for you. The NIB, or Storyboard, just describe a way for those objects to be created. Also some other customisations, like frames, colors, etc.
More about how this ties up can be found here.
Building on Alexander's answer:
UIView objects have a method init(frame:) that lets you create a new UIView object with a specified frame.
Other UIView subclasses might have init methods that take additional parameters.
UIView objects also support an init method init(coder:) that knows how to create an object from a stream of stored data. This is known as "deserializing" the object, or converting it from a byte-stream back into a running object.
When you build an object in a Storyboard or XIB file in Interface Builder, the system serializes the object into a byte stream and saves it into your Storyboard/XIB.
Then when you invoke the storyboard scene/XIB, the system reads the data stream and uses it to recreate (deserialize) the objects that are described in the storyboard/XIB.
The effect is essentially the same as if you wrote a bunch of code that created and configured all your views, but instead of writing all that code you are able to build your interface in Interface Builder, which is faster and easier to create, and MUCH faster and easier to update and maintain than a bunch of custom code.
But I don't see how that translates when using iOS, since I haven't seen any objects being created explicitly yet
There's no difference between the objects in iOS and what you understand objects to be. Objects are instances of a class. What you need to understand is that your own code is not the only place where objects can be created, and your own code will often interact with objects created outside your code. Here's a simple example:
let defaults = NSUserDefaults.standardUserDefaults()
Here defaults gets a reference to a user defaults object that the system provides. You never need to instantiate NSUserDefaults yourself.
Are things like buttons, labels, and sliders objects?
Yes, those are instances of UIButton, UILabel, and UISlider, respectively.
If so, where's the "code" behind them?
It's in the UIKit framework. You don't get to see the source code for those classes, but you can still use them by linking the framework into your app.
Why do buttons, labels, etc. display even before they're connected through outlets and actions to the View Controller?
You're talking about storyboards here. When you set up a view in Xcode's storyboard editor, the data that's stored in the storyboard file is essentially an archive containing serialized objects. When a view controller is instantiated from a storyboard, the objects in the storyboard are recreated from that data and then connected to the view controller's outlets. You can start this process yourself by instantiating a new view controller like this:
let storyboard = UIStoryboard(name: "MyStoryboard", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "MyViewController")
You don't usually need to do that, though, because the segues in your storyboard provide for transitioning between scenes, including creating the view controller that's the destination of a given segue.
I want to design and generate view elements in code, without using storyboard. But I haven't figure out a simple way to locate where should I put my view elements by describing its CGRect. For example, I can drag a button direct into the storyboard and put it where i want, however, in code, I have to write something like
[button setFrame:CGRectMake(24.0f, 113.0f, 271.0f, 140.0f)];
Is there any way or any software can help me?
Your question is a bit broad. You can create your view in code by including in your subclass of UIViewController properties for each subview (control) element and then make all the initialisation in designated initialiser and setting frames and other customisation in viewDidLoad method most likely. But for details where to what set you should go to references for UIViewController lifecycle as it may depend on what you want to achieve. in some cases setting frames in viewDidLoad will be fine whereas sometimes you will need to make some changes in viewDidLayoutSubviews for example.
If I'm creating a UIView programmatically and I wish to change the UIView properties (background, for example, or actually, messing with CALayers), must I place the code outside of UIView such as in the View controller? Can I put the code somewhere inside UIView?
I was checking out the CoreAnimationKioskStyleMenu example, its code is inside UIView but it's loaded from Nib and can be placed at awakeFromNib, so it doesn't seem to apply to my case.
That depends. Obviously, a good way to handle this is to use a xib file, as it is designed to hold data like this, but that isn't always the best answer for every situation.
If the view is meant to be reused frequently (like a button, or some widget) throughout the application, its best to store all that customization in a subclass of the UIView.
If its a single larger view that will always be managed by a UIViewController, you can keep some of the information in the UIViewController. However, if you end up subclassing a UIView anyway it's probably best practice to keep the data in the UIView.
As a general note, I believe its worth your time to push as much of this data into a xib using interface builder. Magic values (like colors or sizes) peppered through your code will always be a problem if you want to modify it. I have found modifying a xib to be much easier.
Actually there are some methods where you could place initialization/ customization code.
(void)willMoveToSuperview:(UIView *)newSuperview;
(void)didMoveToSuperview;
will get called as soon as u add the view as a subview to another view, at which point you already have the frame and all the properties, and you can do further customizing as you wish.
(void)layoutSubviews -- generally used for changing subviews' frames and layout organization.
Will get called each time the view needs to be redrawn by the system, or when you specifically call [self setNeedsLayout] on your UIView.
Hope this helps.
I have a nib file where I have a view that contains a background image, a button and another image that covers the full screen (a shadow) that needs to be moved to the front.
On the view, I'm creating child views, and after creating those and adding them using [self addView] I need to move to the front the shadow image.
I'm currently using the tag attribute to find that view, but I'm thinking there's probably a better way, by means of identifying the subviews I add in Interface Builder by some name.
I tries adding a IBOutlet to connect the subview with its parent, but it didn't work (and made no sense, since the subview is already connected to its parent in some way).
The IBOutlets way should work, and is probably the best way to do it. Make sure you made the proper connection in Interface Builder after you declared them in the .h file.
The iPhone does a lazy loading of view controllers. The nib might not have been loaded in initWithCoder or any init method for that matter as Kendall specified.
viewDidLoad is the preferred place to access anything from the nib if you want to access them before the view is displayed.
Hope that helps.
At what point are you trying to access the subviews? If you try within init of a ViewController, the IBOutlets will be nil. The first method you can get at them is probably viewDidLoad.
The reason it does make sense to do things this way is that IBOutlets are just direct pointers to some component, even if they are already subviews of something else. Just saves a lot of hunting.
Using the Tag is a perfectly valid way to locate specific views, so long as you're using the viewWithTag: method. If you're already using tags, there's no need to change to IBOutlets unless you just don't like calling viewWithTag:.