Currently I have 5 tabs. The third tab is supposed to show a view which takes space only half of the screen, and the rest requires entire screen. I wonder how I can display tab3's view on top of the rest of tabs'. Suppose current selected tab is 2 and the user pressed tab3, it shows tab3's view on top of tab2's view. Is it possible to do that? Or I have to create my own View and fake the tab controller.
Problem Solved;
Not very elegant but it works. I fake Tab 3 with a view controller that has no view and make tabItem 3 as a trigger that presents a view controller.
something like this:
duplicatedTab3 = [[Tab3 alloc] init]
[tabbarController setViewControllers:[NSArray arrayWithObjects:tab1,tab2,tab3,tab4,tab5,nil]];
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
BOOL flag = NO;
NSUInteger tabIndex = [tabBarController.viewControllers indexOfObject:viewController];
if (viewController == [tabBarController.viewControllers objectAtIndex:tabIndex] &&
tabIndex != tabBarController.selectedIndex) {
if ([viewController isMemberOfClass:tab3]) {
if (![duplicatedTab3 isShowing])
[duplicatedTab3 show];
else
[duplicatedTab3 hide];
}
else {
flag = YES;
}
}
return flag;
}
Related
This example is pretty contrived, but it illustrates the behavior. I know you can use .accessibilityIdentifier to uniquely identify a control, but I'm just trying to better understand the interplay between XCUIElement and XCUIElementQuery.
Let's say you have an app like this:
import SwiftUI
struct ContentView: View {
#State var showRedButton = true
var body: some View {
VStack {
if showRedButton {
Button("Click me") {
showRedButton = false
}
.background(.red)
}
else {
HStack {
Button("Click me") {
showRedButton = true
}
.background(.blue)
Spacer()
}
}
}
}
}
And you are UI testing like this:
import XCTest
final class MyAppUITests: XCTestCase {
func testExample() throws {
let app = XCUIApplication()
app.launch()
print(app.debugDescription)
// At this point, the Element subtree shows a single Button:
// Button, 0x14e40d290, {{162.3, 418.3}, {65.3, 20.3}}, label: 'Click me'
let btn = app.buttons["Click me"]
btn.tap() // <-- This tap makes the red button disappear and shows the blue button
print(app.debugDescription)
// Now, the Element subtree shows a single Button that has a different ID
// and different x-y coordinates:
// Button, 0x15dc12e50, {{0.0, 418.3}, {65.3, 20.3}}, label: 'Click me'
btn.tap() // <-- This tap now works on the blue button?? Without requerying?
print(app.debugDescription)
// The red button reappears, but with a different ID (which makes sense).
}
}
Why does the second tap work, even though it's a different control? This must mean that SwiftUI is automatically re-running the XCUIElementQuery to find the button that matches "Click me". Apparently the variable btn isn't linked to the control with the ID 0x14e40d290. Does this mean XCUIElement actually represents an XCUIElementQuery? I expected it to require me to explicitly re-run the query like this,
btn = app.buttons["Click me"]
prior to running the 2nd tap, or the tap would've said that btn was no longer available.
The final print of the Element subtree shows that the red button has a different ID now. This makes sense, because when SwiftUI redraws the red button, it's not the same instance as the last time. This is explained well in the WWDC videos. Nevertheless, at the moment I connected the variable "btn" to the control, I thought there was a tighter affiliation. Maybe UI testing has to behave this way because SwiftUI redraws controls so frequently?
I have a Mac OS menu bar which when clicked opens a popup view, I want to add upon this by incorporating a quit function. When the icon is right clicked another popup menu will be presented.
I am stuck on trying to get the another popup menu to present, I am able to detect a right click on the icon but I unable to show the popup menu view.
Preview when i left click
POPUP MENU VIEW FOR BOTH POPUPS
#main
struct MenuNoteApp: App {
let persistenceController = PersistenceController.shared
#Environment(\.scenePhase) var scenePhase
#NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
.onChange(of: scenePhase) { _ in
persistenceController.save()
}
}
}
extension NSStatusBarButton {
open override func rightMouseUp(with event: NSEvent) {
// Detect right click and prints right click
print("right click")
}
}
class AppDelegate: NSObject,NSApplicationDelegate{
let persistenceController = PersistenceController.shared
// Status Item
var statusItem: NSStatusItem?
// PopOver
var popOver = NSPopover()
var popOverOptions = NSPopover()
func applicationDidFinishLaunching( _ notification: Notification){
// Menu View
let menuView = MenuView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
// Creating PopOver
popOver.behavior = .transient
popOver.animates = true
// Setting Empty View Controller And Setting View as SwiftUI View with help of hosting controller
popOver.contentViewController = NSViewController()
popOver.contentViewController?.view = NSHostingView(rootView: menuView)
popOverOptions.contentViewController = NSViewController()
popOverOptions.contentViewController?.view = NSHostingView(rootView: OptionsView())
// Creating Status Bar Button
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
// Check if status button is available
if let MenuButton = statusItem?.button {
MenuButton.image = NSImage(systemSymbolName: "note", accessibilityDescription: nil)
MenuButton.action = #selector(MenuButtonToggle)
}
}
// Button Action
#objc func MenuButtonToggle(sender: AnyObject){
if popOver.isShown{
popOver.performClose(sender)
}
else if popOverOptions.isShown{
popOverOptions.performClose(sender)
}
else{
//Present Pop Over
if let menuButton = statusItem?.button{
// popOver is the main view while popOverOpitions is the second view
self.popOver.show(relativeTo: menuButton.bounds, of: menuButton, preferredEdge: NSRectEdge.minY)
}
}
}
}
Instead of overriding functions in NSStatusBarButton, you should detect the left and right click behavior within the MenuButtonToggle function.
First, you should register the right-click action, inside your the if-let inside applicationDidFinishLaunching, like so:
if let menuButton = statusItem?.button {
menuButton.image = NSImage(systemSymbolName: "note", accessibilityDescription: nil)
menuButton.action = #selector(MenuButtonToggle)
menuButton.sendAction(on: [.leftMouseUp, .rightMouseUp]) // register action on right click too
}
Then, you want to update the MenuButtonToggle function to act differently depending on left and right clicks.
#objc func menuButtonToggle(sender: AnyObject){
// ... previous logic here
if let event = NSApp.currentEvent {
if event.type == NSEventType.rightMouseUp {
// Right button click
self.popOver.show(...)
} else {
// Left button click
self.popOverOptions.show(...)
}
}
}
I need some direction to resolve a problem. I have multiple videos inside a scroll view and I want only one of them to play at a time. I know about geo.frame to know the position of the view in screen but how can I constantly check if the screen moved to a point? I want to use this code in the video player view but I can only put this to .onAppear and it won't work because it is only called once. Is there a method that I can check when the screen is moved (scroll view is dragged) so that I can play the video in the middle and stop the other ones?
if (geo.frame(in: .global).midY > 200 && geo.frame(in: .global).midY < 800) {
avPlayer.play()
print("Global center: \(geo.frame(in: .global).midX) x \(geo.frame(in: .global).midY)")
print("Video to play: \(postID)")
}
You can put this code inside your view body using the Geometry reader. The value of geo is updated as the items are being scrolled and moved around.
Put this in a view of a row in your table of videos:
struct Example: View {
private func checkLocation(geo: GeometryProxy) -> Bool {
let loc = geo.frame(in: .global).midY
print ("Loc is: \(loc)")
if loc > 200 {
return true
}
return false
}
var body: some View {
GeometryReader { geo in
if checkLocation(geo: geo) {
Text ("This is working")
}
}
}
}
I have a uitabbarcontroller with 4 tab bar items and each tab bar item has a uinavigationcontroller.
I needed to lock the orientation of one uitabbar item only to Portrait. So i implemented the following code:
Created a custom tab bar controller and added the following code in it:
MainTabBarController.m
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// You do not need this method if you are not supporting earlier iOS Versions
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
-(NSUInteger)supportedInterfaceOrientations
{
if (self.selectedViewController)
return [self.selectedViewController supportedInterfaceOrientations];
return UIInterfaceOrientationMaskPortrait;
}
-(BOOL)shouldAutorotate
{
return YES;
}
Created a custom navigation controller to use in one of the uitabbaritems and added the following code in it:
-(NSUInteger)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
-(BOOL)shouldAutorotate
{
return YES;
}
and for the uiviewcontroller in the custom navigation controller i added the following code:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
The above code works fine. My issue is, if you go to the tabbar item (whose orientation is locked to Protrait) when the device is already in Landscape mode the orientation changes to Landscape. Can anyone please help me how to solve my issue.
Thanks,
Anand.
FYI!!!
I've found a way for my problem. I added the following function to the viewcontroller for which i want the display only in protrait mode:
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
if (UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation]))
{
if ([[UIDevice currentDevice] respondsToSelector:#selector(setOrientation:)])
{
int orientationPortrait = UIInterfaceOrientationPortrait;
NSMethodSignature *sig = [[UIDevice currentDevice] methodSignatureForSelector:#selector(setOrientation:)];
NSInvocation* invo = [NSInvocation invocationWithMethodSignature:sig];
[invo setTarget:[UIDevice currentDevice]];
[invo setSelector:#selector(setOrientation:)];
[invo setArgument:&orientationPortrait atIndex:2];
[invo invoke];
}
}
}
I created a message box into the FirstViewController of my TabBarController:
- (void)pressedButton:(id)sender {
[[[UIAlertView alloc] initWithTitle:#"Snap it" message:#"Take a picture"
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil]
show];
When the user clicks on "Ok", I want them to be redirected to the SecondViewController.
Any idea how to do this?
Thanks in advance.
If you have your tab bar controller connected to a "IBOutlet" (or have some other reference to it), switching tabs is as easy as updating the selectedIndex property (I've linked the Apple documentation for you).
EDIT:
Change your code to this.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// if you have connected your tab bar controller to an IBOutlet named myTabBarController
if(myTabBarController)
{
// first tab bar controller is zero, second tab bar controller is 1, etc.
myTabBarController.selectedIndex = 1;
} else {
NSLog( #"tabBarController is nil and probably not set correctly" );
}
}
- (void)pressedButton:(id)sender {
UIAlertView * alertView =
[[UIAlertView alloc] initWithTitle:#"Snap it" message:#"Take a picture" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
if(alertView)
{
[alertView show];
}
}
This assumes you've set your tab bar controller to an IBOutlet correctly. Notice also that I've set the "delegate" on the alert view to "self" (i.e. the "clickedButtonAtIndex" method has to live in the same object & class if using "self" as a delegate).