appium-webdriveragent 16.1.2 → 16.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/WebDriverAgent.xcodeproj/project.pbxproj +297 -0
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationApp_tvOS.xcscheme +78 -0
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationTests_tvOS.xcscheme +86 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.h +20 -8
- package/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.m +116 -60
- package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.h +15 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +16 -0
- package/WebDriverAgentLib/Categories/XCUIElement+FBTVFocuse.m +4 -4
- package/WebDriverAgentLib/Commands/FBAlertViewCommands.m +10 -36
- package/WebDriverAgentLib/Commands/FBElementCommands.m +3 -3
- package/WebDriverAgentLib/FBAlert.h +43 -30
- package/WebDriverAgentLib/FBAlert.m +257 -91
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Routing/FBExceptionHandler.m +8 -1
- package/WebDriverAgentLib/Routing/FBExceptions.h +9 -0
- package/WebDriverAgentLib/Routing/FBExceptions.m +3 -0
- package/WebDriverAgentLib/Routing/FBSession.m +11 -13
- package/WebDriverAgentLib/Utilities/FBAlertsMonitor.m +11 -9
- package/WebDriverAgentLib/Utilities/FBPasteboard.m +8 -4
- package/WebDriverAgentLib/Utilities/FBTVNavigationTracker-Private.h +10 -0
- package/WebDriverAgentLib/Utilities/FBTVNavigationTracker.h +29 -4
- package/WebDriverAgentLib/Utilities/FBTVNavigationTracker.m +62 -27
- package/WebDriverAgentTests/IntegrationApp_tvOS/AppDelegate.h +13 -0
- package/WebDriverAgentTests/IntegrationApp_tvOS/AppDelegate.m +26 -0
- package/WebDriverAgentTests/IntegrationApp_tvOS/Info.plist +41 -0
- package/WebDriverAgentTests/IntegrationApp_tvOS/SceneDelegate.h +15 -0
- package/WebDriverAgentTests/IntegrationApp_tvOS/SceneDelegate.m +22 -0
- package/WebDriverAgentTests/IntegrationApp_tvOS/ViewController.h +13 -0
- package/WebDriverAgentTests/IntegrationApp_tvOS/ViewController.m +79 -0
- package/WebDriverAgentTests/IntegrationApp_tvOS/main.m +17 -0
- package/WebDriverAgentTests/IntegrationTests/FBAlertTests.m +32 -51
- package/WebDriverAgentTests/IntegrationTests/FBIntegrationTestCase.m +9 -1
- package/WebDriverAgentTests/IntegrationTests/FBSafariAlertTests.m +1 -1
- package/WebDriverAgentTests/IntegrationTests_tvOS/FBTVFocusIntegrationTests.m +84 -0
- package/WebDriverAgentTests/IntegrationTests_tvOS/Info.plist +24 -0
- package/WebDriverAgentTests/UnitTests_tvOS/Doubles/FBXCAccessibilityElementDouble.h +27 -0
- package/WebDriverAgentTests/UnitTests_tvOS/Doubles/FBXCAccessibilityElementDouble.m +23 -0
- package/WebDriverAgentTests/UnitTests_tvOS/Doubles/FBXCElementSnapshotDouble.h +43 -0
- package/WebDriverAgentTests/UnitTests_tvOS/Doubles/FBXCElementSnapshotDouble.m +24 -0
- package/WebDriverAgentTests/UnitTests_tvOS/FBTVNavigationTrackerTests.m +48 -0
- package/package.json +1 -1
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
#import "FBXCTestDaemonsProxy.h"
|
|
25
25
|
#import "XCUIApplication+FBQuiescence.h"
|
|
26
26
|
#import "XCUIElement.h"
|
|
27
|
-
#import "XCUIElement+FBClassChain.h"
|
|
28
27
|
|
|
29
28
|
/*!
|
|
30
29
|
The intial value for the default application property.
|
|
@@ -56,14 +55,10 @@ NSString *const FB_SAFARI_BUNDLE_ID = @"com.apple.mobilesafari";
|
|
|
56
55
|
NSString *autoClickAlertSelector = FBConfiguration.autoClickAlertSelector;
|
|
57
56
|
if ([autoClickAlertSelector length] > 0) {
|
|
58
57
|
@try {
|
|
59
|
-
|
|
60
|
-
shouldReturnAfterFirstMatch:YES];
|
|
61
|
-
if (matches.count > 0) {
|
|
62
|
-
[[matches objectAtIndex:0] tap];
|
|
63
|
-
}
|
|
58
|
+
[alert clickElementMatchingClassChain:autoClickAlertSelector];
|
|
64
59
|
} @catch (NSException *e) {
|
|
65
60
|
[FBLogger logFmt:@"Could not click at the alert element '%@'. Original error: %@",
|
|
66
|
-
|
|
61
|
+
autoClickAlertSelector, e.reason];
|
|
67
62
|
}
|
|
68
63
|
// This setting has priority over other settings if enabled
|
|
69
64
|
return;
|
|
@@ -73,14 +68,17 @@ NSString *const FB_SAFARI_BUNDLE_ID = @"com.apple.mobilesafari";
|
|
|
73
68
|
return;
|
|
74
69
|
}
|
|
75
70
|
|
|
76
|
-
NSError *error;
|
|
77
71
|
if ([self.defaultAlertAction isEqualToString:@"accept"]) {
|
|
78
|
-
|
|
79
|
-
[
|
|
72
|
+
@try {
|
|
73
|
+
[alert accept];
|
|
74
|
+
} @catch (NSException *e) {
|
|
75
|
+
[FBLogger logFmt:@"Cannot accept the alert. Original error: %@", e.reason];
|
|
80
76
|
}
|
|
81
77
|
} else if ([self.defaultAlertAction isEqualToString:@"dismiss"]) {
|
|
82
|
-
|
|
83
|
-
[
|
|
78
|
+
@try {
|
|
79
|
+
[alert dismiss];
|
|
80
|
+
} @catch (NSException *e) {
|
|
81
|
+
[FBLogger logFmt:@"Cannot dismiss the alert. Original error: %@", e.reason];
|
|
84
82
|
}
|
|
85
83
|
} else {
|
|
86
84
|
[FBLogger logFmt:@"'%@' default alert action is unsupported", self.defaultAlertAction];
|
|
@@ -213,7 +211,7 @@ static FBSession *_activeSession = nil;
|
|
|
213
211
|
XCUIApplicationState testedAppState = self.testedApplication.state;
|
|
214
212
|
if (testedAppState >= XCUIApplicationStateRunningForeground) {
|
|
215
213
|
NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"%K == %@ OR %K IN {%@, %@}",
|
|
216
|
-
@"elementType", @(XCUIElementTypeAlert),
|
|
214
|
+
@"elementType", @(XCUIElementTypeAlert),
|
|
217
215
|
// To look for `SBTransientOverlayWindow` elements. See https://github.com/appium/WebDriverAgent/pull/946
|
|
218
216
|
@"identifier", @"SBTransientOverlayWindow",
|
|
219
217
|
// To look for 'criticalAlertSetting' elements https://developer.apple.com/documentation/usernotifications/unnotificationsettings/criticalalertsetting
|
|
@@ -52,26 +52,28 @@ static const NSTimeInterval FB_MONTORING_INTERVAL = 2.0;
|
|
|
52
52
|
NSArray<XCUIApplication *> *activeApps = XCUIApplication.fb_activeApplications;
|
|
53
53
|
BOOL didDetectAlert = NO;
|
|
54
54
|
for (XCUIApplication *activeApp in activeApps) {
|
|
55
|
-
XCUIElement *alertElement = nil;
|
|
56
55
|
@try {
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
[delegate didDetectAlert:
|
|
56
|
+
FBAlert *alert = [FBAlert alertWithApplication:activeApp];
|
|
57
|
+
if (alert.isPresent) {
|
|
58
|
+
[delegate didDetectAlert:alert];
|
|
59
|
+
didDetectAlert = YES;
|
|
60
60
|
}
|
|
61
61
|
} @catch (NSException *e) {
|
|
62
62
|
[FBLogger logFmt:@"Got an unexpected exception while monitoring alerts: %@\n%@", e.reason, e.callStackSymbols];
|
|
63
63
|
}
|
|
64
|
-
if (
|
|
65
|
-
didDetectAlert = YES;
|
|
64
|
+
if (didDetectAlert) {
|
|
66
65
|
break;
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
if (!didDetectAlert) {
|
|
71
70
|
@try {
|
|
72
|
-
|
|
73
|
-
if (nil !=
|
|
74
|
-
|
|
71
|
+
XCUIApplication *promptApp = XCUIApplication.fb_limitedAccessPromptApplication;
|
|
72
|
+
if (nil != promptApp) {
|
|
73
|
+
FBAlert *alert = [FBAlert alertWithApplication:promptApp];
|
|
74
|
+
if (alert.isPresent) {
|
|
75
|
+
[delegate didDetectAlert:alert];
|
|
76
|
+
}
|
|
75
77
|
}
|
|
76
78
|
} @catch (NSException *e) {
|
|
77
79
|
[FBLogger logFmt:@"Got an unexpected exception while monitoring alerts: %@\n%@", e.reason, e.callStackSymbols];
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
#import <mach/mach_time.h>
|
|
12
12
|
#import "FBAlert.h"
|
|
13
13
|
#import "FBErrorBuilder.h"
|
|
14
|
+
#import "FBExceptions.h"
|
|
14
15
|
#import "FBMacros.h"
|
|
15
16
|
#import "XCUIApplication+FBHelpers.h"
|
|
16
17
|
#import "XCUIApplication+FBAlert.h"
|
|
@@ -100,10 +101,13 @@
|
|
|
100
101
|
break;
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
[
|
|
104
|
+
@try {
|
|
105
|
+
[[FBAlert alertWithApplication:XCUIApplication.fb_systemApplication] accept];
|
|
106
|
+
} @catch (NSException *e) {
|
|
107
|
+
if (![e.name isEqualToString:FBAlertNotPresentException]) {
|
|
108
|
+
@throw e;
|
|
109
|
+
}
|
|
110
|
+
// No alert is present on this tick - expected on most iterations of this poll loop
|
|
107
111
|
}
|
|
108
112
|
uint64_t timeElapsed = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) - timeStarted;
|
|
109
113
|
if (timeElapsed / NSEC_PER_SEC > timeout) {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
#import <Foundation/Foundation.h>
|
|
10
|
+
#import "FBXCElementSnapshot.h"
|
|
10
11
|
|
|
11
12
|
#if TARGET_OS_TV
|
|
12
13
|
|
|
@@ -22,6 +23,15 @@
|
|
|
22
23
|
|
|
23
24
|
- (FBTVDirection)horizontalDirectionWithItem:(FBTVNavigationItem *)item andDelta:(CGFloat)delta;
|
|
24
25
|
- (FBTVDirection)verticalDirectionWithItem:(FBTVNavigationItem *)item andDelta:(CGFloat)delta;
|
|
26
|
+
- (FBTVDirection)directionWithItem:(FBTVNavigationItem *)item
|
|
27
|
+
delta:(CGFloat)delta
|
|
28
|
+
positiveDirection:(FBTVDirection)positiveDirection
|
|
29
|
+
negativeDirection:(FBTVDirection)negativeDirection;
|
|
30
|
+
|
|
31
|
+
// Exposed for testing: the pure direction-math core of `-pollFocusState:`,
|
|
32
|
+
// parameterized on the already-resolved focused element's snapshot so tests
|
|
33
|
+
// can supply a fake one instead of a live query result.
|
|
34
|
+
- (FBTVDirection)directionTowardsTargetFromFocusedElementSnapshot:(id<FBXCElementSnapshot>)focusedSnapshot;
|
|
25
35
|
@end
|
|
26
36
|
|
|
27
37
|
#endif
|
|
@@ -22,6 +22,19 @@ typedef NS_ENUM(NSUInteger, FBTVDirection) {
|
|
|
22
22
|
FBTVDirectionNone = 4
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
Represents where the tracked target element currently stands with respect
|
|
27
|
+
to keyboard/remote focus, as of the last `-pollFocusState:` call.
|
|
28
|
+
*/
|
|
29
|
+
typedef NS_ENUM(NSUInteger, FBTVFocusState) {
|
|
30
|
+
// The target element currently has focus
|
|
31
|
+
FBTVFocusStateFocused,
|
|
32
|
+
// The target element could not be located in the accessibility tree anymore
|
|
33
|
+
FBTVFocusStateGone,
|
|
34
|
+
// The target element exists, but does not have focus yet
|
|
35
|
+
FBTVFocusStatePending,
|
|
36
|
+
};
|
|
37
|
+
|
|
25
38
|
NS_ASSUME_NONNULL_BEGIN
|
|
26
39
|
|
|
27
40
|
@interface FBTVNavigationItem : NSObject
|
|
@@ -38,12 +51,24 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
38
51
|
+ (instancetype)trackerWithTargetElement: (XCUIElement *) targetElement;
|
|
39
52
|
|
|
40
53
|
/**
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
Determines whether the tracked target element currently has focus, whether
|
|
55
|
+
it still exists, and - if neither - which direction the focus should move
|
|
56
|
+
to get closer to it. Each of these is resolved with the cheapest available
|
|
57
|
+
accessibility round trip for what it's checking: `hasFocus`/`exists` are
|
|
58
|
+
single already-known-element checks, and the currently focused element (if
|
|
59
|
+
any) is found via a targeted `hasFocus == true` query rather than a whole-app
|
|
60
|
+
snapshot walk, whose cost scales with total app size/depth instead of with
|
|
61
|
+
the (typically 0-1) number of matches.
|
|
43
62
|
|
|
44
|
-
@
|
|
63
|
+
@param direction Always reset to `FBTVDirectionNone` first, then set to the
|
|
64
|
+
suggested direction to move the focus to when the return value is
|
|
65
|
+
`FBTVFocusStatePending`. It is left as `FBTVDirectionNone` when the return
|
|
66
|
+
value is `FBTVFocusStateFocused`/`FBTVFocusStateGone`, or when the
|
|
67
|
+
currently focused element could not be determined yet - in the latter
|
|
68
|
+
case the caller should just retry on the next iteration.
|
|
69
|
+
@return The current focus state of the tracked target element
|
|
45
70
|
*/
|
|
46
|
-
- (FBTVDirection)
|
|
71
|
+
- (FBTVFocusState)pollFocusState:(FBTVDirection *)direction;
|
|
47
72
|
|
|
48
73
|
@end
|
|
49
74
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
#import "FBTVNavigationTracker-Private.h"
|
|
11
11
|
|
|
12
12
|
#import "FBMathUtils.h"
|
|
13
|
+
#import "FBXCElementSnapshotWrapper.h"
|
|
13
14
|
#import "XCUIElement+FBCaching.h"
|
|
14
15
|
#import "XCUIElement+FBUtilities.h"
|
|
15
16
|
#import "XCUIElement+FBWebDriverAttributes.h"
|
|
@@ -56,17 +57,49 @@
|
|
|
56
57
|
self = [super init];
|
|
57
58
|
if (self) {
|
|
58
59
|
_targetElement = targetElement;
|
|
59
|
-
|
|
60
|
-
_targetCenter = FBRectGetCenter(frame);
|
|
60
|
+
_targetCenter = FBRectGetCenter(targetElement.wdFrame);
|
|
61
61
|
_navigationItems = [NSMutableDictionary dictionary];
|
|
62
62
|
}
|
|
63
63
|
return self;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
- (FBTVDirection)
|
|
66
|
+
- (FBTVFocusState)pollFocusState:(FBTVDirection *)direction
|
|
67
67
|
{
|
|
68
|
-
|
|
68
|
+
*direction = FBTVDirectionNone;
|
|
69
69
|
|
|
70
|
+
// `hasFocus`/`exists` are cheap, single-element live checks - each round
|
|
71
|
+
// trip only has to serialize the state of one already-known element, not
|
|
72
|
+
// an unbounded subtree, so there's no snapshot-walk win to be had here.
|
|
73
|
+
if (self.targetElement.hasFocus) {
|
|
74
|
+
return FBTVFocusStateFocused;
|
|
75
|
+
}
|
|
76
|
+
if (!self.targetElement.exists) {
|
|
77
|
+
return FBTVFocusStateGone;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Likewise, a `hasFocus == true` predicate query only has to serialize its
|
|
81
|
+
// (typically 0-1) matches back over the round trip, unlike a whole-app
|
|
82
|
+
// snapshot walk, whose cost scales with total app size/depth.
|
|
83
|
+
XCUIElement *focusedElement = (XCUIElement *)XCUIApplication.fb_activeApplication.fb_focusedElement;
|
|
84
|
+
if (nil == focusedElement) {
|
|
85
|
+
// Nothing is focused yet (e.g. right after the app became active) - let
|
|
86
|
+
// the caller retry on the next iteration.
|
|
87
|
+
return FBTVFocusStatePending;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// The snapshot was already taken as a side effect of resolving the query
|
|
91
|
+
// above, so grab it for free instead of paying for a new round trip.
|
|
92
|
+
id<FBXCElementSnapshot> focusedSnapshot = focusedElement.lastSnapshot
|
|
93
|
+
?: focusedElement.fb_cachedSnapshot
|
|
94
|
+
?: [focusedElement fb_customSnapshot];
|
|
95
|
+
*direction = [self directionTowardsTargetFromFocusedElementSnapshot:focusedSnapshot];
|
|
96
|
+
|
|
97
|
+
return FBTVFocusStatePending;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
- (FBTVDirection)directionTowardsTargetFromFocusedElementSnapshot:(id<FBXCElementSnapshot>)focusedSnapshot
|
|
101
|
+
{
|
|
102
|
+
FBXCElementSnapshotWrapper *focused = [FBXCElementSnapshotWrapper ensureWrapped:focusedSnapshot];
|
|
70
103
|
CGPoint focusedCenter = FBRectGetCenter(focused.wdFrame);
|
|
71
104
|
FBTVNavigationItem *item = [self navigationItemWithElement:focused];
|
|
72
105
|
CGFloat yDelta = self.targetCenter.y - focusedCenter.y;
|
|
@@ -83,7 +116,6 @@
|
|
|
83
116
|
direction = [self verticalDirectionWithItem:item andDelta:yDelta];
|
|
84
117
|
}
|
|
85
118
|
}
|
|
86
|
-
|
|
87
119
|
return direction;
|
|
88
120
|
}
|
|
89
121
|
|
|
@@ -99,7 +131,7 @@
|
|
|
99
131
|
if (nil != item) {
|
|
100
132
|
return item;
|
|
101
133
|
}
|
|
102
|
-
|
|
134
|
+
|
|
103
135
|
item = [FBTVNavigationItem itemWithUid:uid];
|
|
104
136
|
[self.navigationItems setObject:item forKey:uid];
|
|
105
137
|
return item;
|
|
@@ -107,32 +139,35 @@
|
|
|
107
139
|
|
|
108
140
|
- (FBTVDirection)horizontalDirectionWithItem:(FBTVNavigationItem *)item andDelta:(CGFloat)delta
|
|
109
141
|
{
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return FBTVDirectionRight;
|
|
115
|
-
}
|
|
116
|
-
if (delta < -DBL_EPSILON &&
|
|
117
|
-
![item.directions containsObject: [NSNumber numberWithInteger: FBTVDirectionLeft]]) {
|
|
118
|
-
[item.directions addObject: [NSNumber numberWithInteger: FBTVDirectionLeft]];
|
|
119
|
-
return FBTVDirectionLeft;
|
|
120
|
-
}
|
|
121
|
-
return FBTVDirectionNone;
|
|
142
|
+
return [self directionWithItem:item
|
|
143
|
+
delta:delta
|
|
144
|
+
positiveDirection:FBTVDirectionRight
|
|
145
|
+
negativeDirection:FBTVDirectionLeft];
|
|
122
146
|
}
|
|
123
147
|
|
|
124
148
|
- (FBTVDirection)verticalDirectionWithItem:(FBTVNavigationItem *)item andDelta:(CGFloat)delta
|
|
125
149
|
{
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
150
|
+
return [self directionWithItem:item
|
|
151
|
+
delta:delta
|
|
152
|
+
positiveDirection:FBTVDirectionDown
|
|
153
|
+
negativeDirection:FBTVDirectionUp];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
- (FBTVDirection)directionWithItem:(FBTVNavigationItem *)item
|
|
157
|
+
delta:(CGFloat)delta
|
|
158
|
+
positiveDirection:(FBTVDirection)positiveDirection
|
|
159
|
+
negativeDirection:(FBTVDirection)negativeDirection
|
|
160
|
+
{
|
|
161
|
+
// CGFloat is double in 64bit. tvOS is only for arm64
|
|
162
|
+
NSNumber *positiveDirectionNumber = [NSNumber numberWithInteger:positiveDirection];
|
|
163
|
+
NSNumber *negativeDirectionNumber = [NSNumber numberWithInteger:negativeDirection];
|
|
164
|
+
if (delta > DBL_EPSILON && ![item.directions containsObject:positiveDirectionNumber]) {
|
|
165
|
+
[item.directions addObject:positiveDirectionNumber];
|
|
166
|
+
return positiveDirection;
|
|
131
167
|
}
|
|
132
|
-
if (delta < -DBL_EPSILON &&
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return FBTVDirectionUp;
|
|
168
|
+
if (delta < -DBL_EPSILON && ![item.directions containsObject:negativeDirectionNumber]) {
|
|
169
|
+
[item.directions addObject:negativeDirectionNumber];
|
|
170
|
+
return negativeDirection;
|
|
136
171
|
}
|
|
137
172
|
return FBTVDirectionNone;
|
|
138
173
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import <UIKit/UIKit.h>
|
|
10
|
+
|
|
11
|
+
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
|
12
|
+
|
|
13
|
+
@end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import "AppDelegate.h"
|
|
10
|
+
#import "SceneDelegate.h"
|
|
11
|
+
|
|
12
|
+
@implementation AppDelegate
|
|
13
|
+
|
|
14
|
+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
|
15
|
+
{
|
|
16
|
+
return YES;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options
|
|
20
|
+
{
|
|
21
|
+
UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
|
|
22
|
+
configuration.delegateClass = SceneDelegate.class;
|
|
23
|
+
return configuration;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
6
|
+
<string>en</string>
|
|
7
|
+
<key>CFBundleExecutable</key>
|
|
8
|
+
<string>$(EXECUTABLE_NAME)</string>
|
|
9
|
+
<key>CFBundleIdentifier</key>
|
|
10
|
+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
11
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
12
|
+
<string>6.0</string>
|
|
13
|
+
<key>CFBundleName</key>
|
|
14
|
+
<string>$(PRODUCT_NAME)</string>
|
|
15
|
+
<key>CFBundlePackageType</key>
|
|
16
|
+
<string>APPL</string>
|
|
17
|
+
<key>CFBundleShortVersionString</key>
|
|
18
|
+
<string>1.0</string>
|
|
19
|
+
<key>CFBundleSignature</key>
|
|
20
|
+
<string>????</string>
|
|
21
|
+
<key>CFBundleVersion</key>
|
|
22
|
+
<string>1</string>
|
|
23
|
+
<key>UILaunchScreen</key>
|
|
24
|
+
<dict/>
|
|
25
|
+
<key>UIApplicationSceneManifest</key>
|
|
26
|
+
<dict>
|
|
27
|
+
<key>UIApplicationSupportsMultipleScenes</key>
|
|
28
|
+
<false/>
|
|
29
|
+
<key>UISceneConfigurations</key>
|
|
30
|
+
<dict>
|
|
31
|
+
<key>UIWindowSceneSessionRoleApplication</key>
|
|
32
|
+
<array>
|
|
33
|
+
<dict>
|
|
34
|
+
<key>UISceneConfigurationName</key>
|
|
35
|
+
<string>Default Configuration</string>
|
|
36
|
+
</dict>
|
|
37
|
+
</array>
|
|
38
|
+
</dict>
|
|
39
|
+
</dict>
|
|
40
|
+
</dict>
|
|
41
|
+
</plist>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import <UIKit/UIKit.h>
|
|
10
|
+
|
|
11
|
+
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
|
|
12
|
+
|
|
13
|
+
@property (strong, nonatomic) UIWindow *window;
|
|
14
|
+
|
|
15
|
+
@end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import "SceneDelegate.h"
|
|
10
|
+
#import "ViewController.h"
|
|
11
|
+
|
|
12
|
+
@implementation SceneDelegate
|
|
13
|
+
|
|
14
|
+
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions
|
|
15
|
+
{
|
|
16
|
+
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
|
17
|
+
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
|
|
18
|
+
self.window.rootViewController = [ViewController new];
|
|
19
|
+
[self.window makeKeyAndVisible];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import <UIKit/UIKit.h>
|
|
10
|
+
|
|
11
|
+
@interface ViewController : UIViewController
|
|
12
|
+
|
|
13
|
+
@end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import "ViewController.h"
|
|
10
|
+
|
|
11
|
+
static NSInteger const FBGridSize = 3;
|
|
12
|
+
static CGFloat const FBCellSide = 200;
|
|
13
|
+
static CGFloat const FBCellSpacing = 60;
|
|
14
|
+
|
|
15
|
+
@interface ViewController ()
|
|
16
|
+
@property (nonatomic, strong) UILabel *lastSelectedLabel;
|
|
17
|
+
@end
|
|
18
|
+
|
|
19
|
+
@implementation ViewController
|
|
20
|
+
|
|
21
|
+
- (void)viewDidLoad
|
|
22
|
+
{
|
|
23
|
+
[super viewDidLoad];
|
|
24
|
+
self.view.backgroundColor = UIColor.whiteColor;
|
|
25
|
+
|
|
26
|
+
UIStackView *rows = [UIStackView new];
|
|
27
|
+
rows.axis = UILayoutConstraintAxisVertical;
|
|
28
|
+
rows.spacing = FBCellSpacing;
|
|
29
|
+
rows.alignment = UIStackViewAlignmentCenter;
|
|
30
|
+
rows.translatesAutoresizingMaskIntoConstraints = NO;
|
|
31
|
+
[self.view addSubview:rows];
|
|
32
|
+
|
|
33
|
+
for (NSInteger row = 0; row < FBGridSize; row++) {
|
|
34
|
+
UIStackView *columns = [UIStackView new];
|
|
35
|
+
columns.axis = UILayoutConstraintAxisHorizontal;
|
|
36
|
+
columns.spacing = FBCellSpacing;
|
|
37
|
+
for (NSInteger column = 0; column < FBGridSize; column++) {
|
|
38
|
+
UIButton *cell = [self newCellWithIdentifier:[NSString stringWithFormat:@"cell_%ld_%ld", (long)row, (long)column]];
|
|
39
|
+
[columns addArrangedSubview:cell];
|
|
40
|
+
}
|
|
41
|
+
[rows addArrangedSubview:columns];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
UIButton *disabledButton = [self newCellWithIdentifier:@"disabledButton"];
|
|
45
|
+
disabledButton.enabled = NO;
|
|
46
|
+
[rows addArrangedSubview:disabledButton];
|
|
47
|
+
|
|
48
|
+
self.lastSelectedLabel = [UILabel new];
|
|
49
|
+
self.lastSelectedLabel.accessibilityIdentifier = @"lastSelectedLabel";
|
|
50
|
+
self.lastSelectedLabel.text = @"none";
|
|
51
|
+
self.lastSelectedLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
|
52
|
+
[self.view addSubview:self.lastSelectedLabel];
|
|
53
|
+
|
|
54
|
+
[NSLayoutConstraint activateConstraints:@[
|
|
55
|
+
[rows.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
|
|
56
|
+
[rows.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
|
|
57
|
+
[self.lastSelectedLabel.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
|
|
58
|
+
[self.lastSelectedLabel.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-FBCellSpacing],
|
|
59
|
+
]];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
- (UIButton *)newCellWithIdentifier:(NSString *)identifier
|
|
63
|
+
{
|
|
64
|
+
UIButton *cell = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
65
|
+
cell.accessibilityIdentifier = identifier;
|
|
66
|
+
[cell setTitle:identifier forState:UIControlStateNormal];
|
|
67
|
+
cell.translatesAutoresizingMaskIntoConstraints = NO;
|
|
68
|
+
[cell.widthAnchor constraintEqualToConstant:FBCellSide].active = YES;
|
|
69
|
+
[cell.heightAnchor constraintEqualToConstant:FBCellSide].active = YES;
|
|
70
|
+
[cell addTarget:self action:@selector(didSelectCell:) forControlEvents:UIControlEventPrimaryActionTriggered];
|
|
71
|
+
return cell;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
- (void)didSelectCell:(UIButton *)cell
|
|
75
|
+
{
|
|
76
|
+
self.lastSelectedLabel.text = cell.accessibilityIdentifier;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import <UIKit/UIKit.h>
|
|
10
|
+
|
|
11
|
+
#import "AppDelegate.h"
|
|
12
|
+
|
|
13
|
+
int main(int argc, char * argv[]) {
|
|
14
|
+
@autoreleasepool {
|
|
15
|
+
return UIApplicationMain(argc, argv, nil, NSStringFromClass(AppDelegate.class));
|
|
16
|
+
}
|
|
17
|
+
}
|