How to hide and show WKInterfaceGroup programmatically with animation? - ios

The solution in this question* uses setHidden to hide and unhide a WKInterfaceGroup:
atypeofGroup.setHidden(true)
atypeofGroup.setHidden(false)
But the problem is, the group will appear and disappear abruptly, it doesn't look professional. Can someone guide me please? Not sure whether it is related to this:
atypeofGroup.animationDidStart(anim: CAAnimation!)
*hide and show WKInterfaceGroup programmatically

This is a great question, but it just isn't possible to animate a change between two groups with the current implementation of WatchKit. I definitely wish it was as well.
The only options you have are to switch interface controllers entirely through the reloadRootControllersWithNames:contexts: or to show/hide a couple of groups using the approach you listed first. Here's a small example of how you could switch from a SimpleInterfaceController to a FirstInterfaceController and SecondInterfaceController in a page set.
class SimpleInterfaceController : WKInterfaceController {
override func willActivate() {
super.willActivate()
let names = ["FirstInterfaceIdentifier", "SecondInterfaceIdentifier"]
WKInterfaceController.reloadRootControllersWithNames(names, contexts: nil)
}
}
I am not sure where you found the following code snippet, but it is certainly not part of the public APIs on WKInterfaceGroup.
atypeofGroup.animationDidStart(anim: CAAnimation!)
While I understand none of these answers are ideal, they are all we have access to at the moment. If you have the time, I'd suggest filing a feature request on Apple's bug reporting system.

Related

Automatically detecting UIButton clicks throughout a View Controller for designing an Analytics SDK - iOS

In our app, we need to detect each and every user click on all the UIButtons inside each and every ViewController. Our project is massive, with many UIViewControllers, each having many UIButtons inside. I have added a analyticsEvent String to the UIButton class inside an extension using Objc's AssociatedObjects, and the desired behavior is to send that string to the server once the button is clicked. I would like to know how would you go about handling this.
1- Adding a click-target for each and every button manually. I don't like this, takes ages. And it makes the code base smell like a swamp.
2- Make a custom UIBUtton instance, do something in it and have all the UIButtons in the app use that. I don't like this, as there are many buttons and it'd need massive refactoring.
3- Override some base function using extensions to understand when a UIButton is tapped. For example, I tried this:
extension UIButton {
open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let result = super.point(inside: point, with: event)
if result {
print("SangriaButtonBounds", "Send event to the server")
}
return result
}
}
This is dangerous, and sometimes gets fired multiple times, which is expected as its usage is for sth else.
4- Another thing is to maybe add a click-target whenever the button is initialized using extensions, but some parts of the codebase are sometimes deleted all the targets on an Object and adding them, and it'd need special handling in such case.
I guess there is some function out there which I can add some behavior to it using the extensions to it, but not sure what it is.
I would like to know what would be the best approach to this in your opinion.
I hate to tell you this, but the correct answer is to override and create a custom UIButton subclass where you can put custom logic in.
What you're doing is attempting to add custom functionality to all objects of a certain type, this is exactly what OOP is designed to be great at.
It's a big job to be sure, but doing anything else like overriding something lower-level like UIControl could lead to extremely hard-to-find bugs every time someone tries to touch, for instance, a UISwitch and gets a side effect.

How to refresh the multi-language controls for each view

I need to do multiple language switching within the app
Need to stay in the current view after switching the language
And my entire application UI needs to be updated to see the replaced language
I found that many people's approach is to reset the root view, which means that you want to re-create the view, the data need to re-request, etc.
I think this is very unreasonable
Do not you use Notification, is there any other way?
If there is a valid link and demo reference is even better
Thank you very much
I'm not sure on how your app works, but why don't you just try something like this:
func updateLanguage() {}
You can use dictionaries to help you to easily update your content. I've made a simple example so you can understand what I'm talking about:
enum Languages {
case english
case portuguese
}
var myLabel = UILabel()
let myLabelText : [Languages : String] = [
Languages.english : "My Label",
Languages.portuguese : "Minha Etiqueta"
]
func updateLanguage(to language : Languages) {
// Updates myLabel's text in real time:
myLabel.text = myLabelText[language]
// Update other UI elements below...
}
Whenever you call the method "updateLanguage", your UI elements will change it's text value in real time, no need to reload anything or to use another "hacks", it's pretty simple and straightforward, I have this approach on my apps, and it just works.
Hopefully that helps!
Try loadView() to refresh same view controller. And add same method in other vc's viewwillappear from which u have pushed that vc .

iOS UITest - Navigate to all available screens

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

How can I detect a subview being added anywhere in the view hierarchy (including subviews)?

I am creating a framework for the other apps to use it. I want to find when the display presented to the user changes. These changes include addition and removal of subviews, scrolling down, adding text, etc. Is there a way I can directly check when the content presented on the screen is changing. Above question is a part of the problem.
Did you mean viewDidLoad?
That function called first time after all view loaded same as ViewTreeObserver.OnGlobalLayoutListener.
After you explanations I would simply do something like this:
class MyViewController:UIScrollViewDelegate{
func addSubview(){
self.takeSnaphot()
}
func scrollViewDidScroll(scrollView:UIScrollView){
self.takeSnaphot()
}
func takeSnaphot(){
//the code to take snaphots
}
}

Test whether a UIView is hidden using XCUITest

How do I test whether a view is hidden using XCUITest? A view is sometimes hidden (set in Xcode like this: Hidden view)
How do I test for that in XCUITest using Swift? In my case, the view is just a label. I tried something like this: XCTAssertFalse(app.staticTexts["pushNotificationInstruction"].accessibilityElementsHidden) . But that's not it. accessibilityElementsHidden is not the same as the view is hidden. Thanks.
Unfortunately, this is not currently possible using XCUITest. Here is a developer forum thread where an Apple engineer suggested filing a radar for this exact issue:
https://forums.developer.apple.com/message/46271
I have personally filed a couple radars relating to the limitations imposed by not being able to access certain properties of UIViews from within an XCUITest. I encourage you to do the same and provide details of the scenarios you are blocked from testing because of this deficiency in XCUITest.
You can assert that the view does not exists and use another test to check the scenario when it does exists. Maybe a bit fragile, but that would cover you case.
let viewControllerShown = app.otherElements["view_myviewcontroller"].waitForExistence(timeout: 5)
XCTAssert(viewControllerShown)
let instructionViewExists = app.staticTexts["pushNotificationInstruction"].exists
XCTAssertFalse(instructionViewExists)
An expedient solution is to carry the visibility state of the view in its accessibilityidentifier.
In your view controller:
view.isHidden = hideView
view.accessibilityidentifier = "view1"+(hideView ? "hidden" : "")
In your tests:
XCTAssert(app.otherElements["view1"].exists)
or
XCTAssertFalse(app.otherElements["view1"].exists)
Looking at the documentation of exist:
The fact that an element exists does not imply that it is hittable.
Elements can exist offscreen, or exist onscreen but be hidden by
another element, causing their isHittable property to return false.
It means that you can check:
if uiElement.exists && uiElement.isHittable {
XCTFail()
}

Resources