I am wondering what RestorationIdentifier is, and why would we use it? I saw RestorationIdentifier on MMDrawerController.
MMDrawerController using like this : `
[self setRestorationIdentifier:#"MMExampleCenterControllerRestorationKey"];`
Consider that you want to allow your user to close the app and then return to exactly where they were when they open the app again. And you should want to do that. Broadly you have 2 options:
Implement it yourself, saving everything into user defaults or similar and reconstructing the view hierarchy yourself
Use Apple State Preservation which will automatically rebuild the view hierarchy for you and which you can tie into to save and restore other pertinent information
Option 2 is behind the use of the restoration id (so that the view hierarchy can be recorded and rebuilt).
It is a property of UIViewController which indicates whether the ViewController and its contents should be preserved and is also used to identify the ViewController during the restoration/relaunch process.
Ref : https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instp/UIViewController/restorationIdentifier
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
Apologies, if this is a little unclear - I'm a noob when it comes to iOS programming. Here's the scenario:
I've got a LogInView, a CategoryView, a CheckerView, a WalkthroughView, and a LandingPageView.
The user starts at LogInView, and depending on the app's bluetooth state, and whether or not the user has been registered, either goes to:
CheckerView (Registered, Bluetooth Off)
LandingPageView (Registered, Bluetooth On)
CategoryView (Unregistered)
If the user hits CategoryView, depending on the state of his bluetooth connection, he goes to either (this part, so far, works okay):
WalkthroughView (Bluetooth on)
CheckerView (Bluetooth off)
The catch is that CategoryView will always go through WalkthroughView, regardless of whether or not bluetooth is on. So, here's what my storyboard looks like:
A right hot mess, I know. Since both LogInView and CategoryView can, at some point, go into CheckerView, I need a way to check which of the segues was used, such that:
CheckerView will always go into LandingView if the previous view was LogInView, and
It will always go into WalkthroughView if the previous view was CategoryView.
I'm vaguely aware of a prepareForSegue function, but I've no idea yet how to use it, nor where to put it (from the previous page, or on the receiving page?)
Any suggestions? Thanks.
It sounds like you're testing conditions to determine where you'll segue. If that's the case, perhaps you could test conditions (registered/unregistered, Bluetooth enabled/disabled). Based on the various conditions, you could use performSegueWithIdentifier to determine where to go next and set up the next ViewController in prepareForSegue using the a segue identifier, rather than "looking back" to see where you came from.
My standard suggestion is that once application state becomes complex it should be moved out of view controllers and into an actual Data Model object.
The Data Model can either be a custom class you create (preferred for scalability). Or, in this case where there's not a great volume of information being shared, you could look at putting it into NSUserDefaults and reading from there when needed.
I am working on an application which allows users to work with a couple of workmodes. Main view of the app contains information common to all workmodes. I want to create a "subview" with ability to change its ViewController. This subview will be used to display information connected with specified workmode. It is important that app goes to MainViewController from WorkmodesViewController in which user chooses workmode to work with.
My question is:
Which tehnique should I use to acheave changeable WorkmodeViewController inside MainViewVontroller
I have found example git project with functionality I need:
https://github.com/mluton/EmbeddedSwapping
What ways are there to change views other than using a navigation-based app? I want my first view to be basically just a simple form and when the user hits a "submit" button I want to send him over to the main view, without being able to return. And I don't want the bar on the top of the view either.
How can I achieve this, and if possible without losing the animations that come with a navigation-based app.
If you wanted your app to be entirely navigation controller free, you can use one of the signatures of presentModalViewController:animated: from whichever UIViewController you deem best fit to be the parent. Call [self dismissModalViewControllerAnimated:YES] on the child view (form you want submitted) after you've handled state change on submit. One thing to watch out with this, is as of iOS 5, Apple now prefers that you use presentViewController: instead, and presentModalViewController: is marked for deprecation at a future date.
In terms of "how you would know the user submitted the form, so they may now proceed in your application" - one way you could do that is to use delegation / notifications to maintain awareness of the state of the form. When the child form is submitted, you can call the parentViewController's delegate callback to set flags - or return authentication data, for example - in your AppDelegate or some high-level class. Delegation and Notifications are useful tools when using the iOS SDK.
Option two could be using a completion handler in with your call to present the child, such as:
ChildForm *childFormWithSubmit = [[ChildForm alloc] init];
[self presentModalViewController:childFormWithSubmit animated:YES
completion:^(/*inlineFunctionParams*/)
{ /*inlineFunctionBodyToRunOnCompletion*/ }];
Lots of possibilities ~
Did you look at 'presentViewController:animated:completion:' in the UIViewController class description? There are lots of options for how you animate in another viewController.
Sled, you can simply just hide the UINavigationBar for your UINavigationController.
That way you won't see the UINavigationBar and the user will not be able to return back to that page.
You'll need to set a permanent flag in your app either writing to text file or using NSUserDefaults.
This may be impossible, but I'm trying to save the state of my application between scene transitions, but I can't figure out what to do. Currently I love the way that when you have an application running and hit the home button, you can go back to that application just where you left off, but if you transition between scenes (in a storyboard), once you get back to that scene the application state was not saved.
I only have two different scenes that need to be saved (you transition back and forth from one to the other). How can I go about saving a storyboard scenes state without taking up precise memory?
More Detailed: Here is my entire storyboard. You transition back and forth between scenes using the plus toolbar button. On the second scene the user can tap on the table view cells and a real image will fill the image view (See figure 1.2)
Figure 1.1
In figure 1.2 you see what happens when you tap inside one of the many table view cells (an image view pops up.)
Figure 1.2
THE PROBLEM: When you tap a table view cell, which fills an image view (shown in figure 1.2) it works fine if you stay on that scene or even hit the iPhone home button (if you hit the iPhone home button and then reopen the app the scene's state was saved and the image view filled with a simple image still shows just like we left it), but if I transition (using the plus button) back to the first scene, and then use the plus button on the first scene to get back to the second scene the image view that I created (shown in figure 1.2) disappears and the second scene loads without saving the state and image views we filled.
EDIT: I tried using the same view controller for both scenes, but it didn't solve the problem.
UPDATE: I just found the following code (that I think stores a views state). How could I use this and is this what I've been looking for?
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
I would suggest a combination of two things:
1. Take DBD's advice and make sure that you don't continuously create new views
2. Create a shared class that is the data controller (for the golfers, so that the data is independent of the scene)
The correct way to make the segues would be to have one leading from the view controller on the left to the one on the right. However, to dismiss the one on the right you can use
-(IBAction)buttonPushed:(id)sender
[self dismissModalViewControllerAnimated:YES];
}
This will take you back the the view controller on the left, with the view controller on the left in its original state. The problem now is how to save the data on the right.
To do this, you can create a singleton class. Singleton classes have only one instance, so no matter how many times you go to the view controller on the right, the data will always be the same.
Singleton Class Implementation (Of a class called DataManager) - Header
#interface DataManager : NSObject {
}
+(id)initializeData;
-(id)init;
#end
Singleton Class Implementation (Of a class called DataManager) - Main
static DataManager *sharedDataManager = nil;
#implementation DataManager
+(id)initializeData {
#synchronized(self) {
if (sharedDataManager == nil)
sharedDataManager = [[self alloc] init];
}
return sharedDataManager;
}
-(id)init {
if(self == [super init]) {
}
return self;
}
#end
Then, inside your view controller code you can grab this instance like this
DataManager *sharedDataManager = [DataManager initializeDataManager];
This way you will have the same data no matter how many times you switch views.
Also, you can better adhere to MVC programming by keeping you data and your view controllers separate. (http://en.wikipedia.org/wiki/Model–view–controller)
Figure 1.1 has a fundamental flaw which I believe the basis of your problem.
Segues (the arrows between controllers on the storyboard) create new versions of the UIViewControllers. You have circular segues. So when you go "back" to the original screen through the segue is really taking you forward by creating a new version.
This can create a major problem for memory usage, but it also means you can't maintain state because each newly created item is an empty slate.
Since your are using a UINavigationController and pushViewController:animated: you should "pop" your controller to get rid of it.
On your "second" scene, remove the segue from the + button and create an IBAction on a touchUpInside event. In the IBAction code add the "pop"
- (IBAction)plusButtonTapped {
[self.navigationController popViewControllerAnimated:YES];
}
I see what you mean. This should happen to every application, as when the last view controller in the navigation stack is transitioned away from, it is deallocated and freed. If you need to save values such as text or object positions, a plist may be the way to go. See this related question for how to use a plist.
Apple isn't going to do this for you. You should probably just save the state of each view using NSUserDefaults and each time your application launches re-load your saved data.
If you are storing everything in CoreData you would only need to save the active view and a few object ids, if not you would need to save any data you have.
Don't expect iOS to save anything that you have in memory between launches. Just store it in NSUserDefaults and load it each time.
Store the state of the scene in NSUserDefaults or inside a plist file then when loading up the scene just load it with the settings from there. If the images are loaded from the internet you might also want to save them locally on your iphones hard drive so it runs a bit smoother.
I don't think you should cycle the segues, just use one that connects viewcontroller 1 from viewcontroller 2 should be enough and that way you make sure that no additional viewcontrollers are being made (memory problems maybe?)
However for your particular problem, I believe that you should use core data to save the exact state of your table, view because ios doesn't save the exact state of view at all times. it will require work but you will achieve what you want. You will need to save the exact photo( using a code or enums that will be saved), the location in the table view, the score or well whatever data you need to save that state.
The best of all is that coredata is so efficient that reloading the data when the app is relaucnhed or into foreground it takes no time, and ive used core data to load more than 5k of records until now and works just fine and its not slow at all.
When i get back home ill provide a code you might use to get an idea of what i mean.
The key here is to:
Have some sort of storage for the data that your application needs. This is your application's data model.
Give each view controller access to the model, or at least to the part of the model that it needs to do its job. The view controller can then use the data from the model to configure itself when it's created, or when the view is about to appear.
Have each view controller update the model at appropriate times, such as when the view is about to disappear, or even every time the user makes a change.
There are a lot of ways that you can organize your data in memory, and there are a lot of ways that you can store it on disk (that is, in long term storage). Property lists, Core Data, plain old data files, and keyed archives are all possibilities for writing the data to a file. NSArray, NSDictionary, NSSet, and so on are all classes that you can use to help you organize your data in memory. None of that has anything to do with making your view controllers feel persistent, though. You'll use them, sure, but which one you choose really doesn't matter as far as updating your view controllers goes. The important thing, again, is that you have some sort of model, and that your view controllers have access to it.
Typically, the app delegate sets up the model and then passes it along to the view controllers as necessary.
Something else that may help is that you don't have to let your view controller(s) be deleted when they're popped off the navigation stack. You can set up both view controllers in your app delegate, if you want, so that they stick around. You can then use the ones you've got instead of creating new ones all the time, and in so doing you'll automatically get some degree of persistence.