I want to create a mobile app (with possible desktop use) that uses a context menu and has as close to a native look and feel as possible for both Android and iOS. (This is my first foray into both Qt and QML.)
I figured out how to create a Menu and call myMenu.popup() to show the context menu. And in Android this context menu looks very similar to a native android context menu. This context menu also looks native on the desktop. The problem comes with iOS.
iOS has a similar concept to context menus called actionsheets. Examples. But the contextMenu looks like a windows context menu (right click menu) floating on the window.
tl;dr;
Is there a way to get the Menu in qml to look similar to iOS actionsheets when run on a iOS device? I have searched for hours today and can't find anything.
code:
The Menu code is mostly copied from the Qt docs just to see how things look and work
Menu
{
id: myContextMenu
title: "Edit"
MenuItem {
text: "Cut"
onTriggered: {console.log("cut")}
}
MenuItem {
text: "Copy"
onTriggered: {console.log("copy")}
}
MenuItem {
text: "Paste"
onTriggered: {console.log("paste")}
}
MenuSeparator { }
Menu {
title: "More Stuff"
MenuItem {
text: "Do Nothing"
}
}
}
MouseArea {
id: longPressArea
anchors.fill: text
onClicked: {
myContextMenu.popup()
}
}
Summarizing the comments above: No, not in the current version of Qt, unless you roll your own in QML.
Quick Controls uses one of [native, QWidget, QML] implementations, whichever is found first. You can read the source to see that there is no native implementation: grep for createPlatformMenu() in Qt/../Src/qtbase/src/plugins/platforms/ios. Thats where the adaptors to native widgets are.
Another answer is: you could contribute by creating the adaptor to the native widget for iOS (if you are an iOS and C++ programmer.) Also assuming that a UIActionSheet is the proper widget to adapt (it seems so.)
I suppose your concern is that a centered menu (instead of a native one that animatedly slides onto the screen like a drawer, the feel) doesn't meet the HIG (or that the style/look is wrong.) Thats a moving target. The iOS8 documentation under showInView seems to say a centered popup menu is an option (at least on iPad, its unclear whether it would work on a phone.) And its fuzzy what the store would reject.
Isn't that an intended benefit of QML: you could provide different skins for tablet and phone?
With Qt Labs, since Qt5.8, you can get native looking menus on macOS, iOS, Android and Linux (gtk+). Haven't tried it myself yet, but you can take a look:
https://doc.qt.io/qt-5.12/qml-qt-labs-platform-menu.html
You need to import Qt.labs.platform 1.1, link against widgets with QT += widgets and use a QApplication instead of QGuiApplication.
For comparison, here is also the stable menu from qml which does not try to look native.
https://doc.qt.io/qt-5.12/qml-qtquick-controls2-menu.html
Related
Im using Gluon to develop javafx applications to Android, Iphone (and to desktop). When I export a test application to my Android phone (Marshmallow 6.0) - I cannot hold down onto text to access the menu from where you can copy text (the context menu)
(Which is an example of what you can do with a context menu - and is not a question of how to copy text on long hold specifically in Android).
This was possible on iphone 6 when testing it there.
How can I detected wether the device/operating system has a default context menu or not in java?
On Desktop there is a default ContextMenu that is created and installed in TextFieldBehavior (private API). If you don't set your own custom context menu, that will be the one used when a ContextMenuEvent is fired (with a right click event for instance).
On mobile, both Android and iOS have a ContextMenu as well.
On iOS, it uses a native TextField (UITextField). When the long press event happens, it triggers the default context menu (on my iPad I can see a small magnifying glass, and after that the context menu shows up).
On Android, the JavaFX TextField has a custom skin, but shares the same private TextFieldBehavior as the desktop version. The problem in this case is the missing right click event that would trigger the ContextMenuEvent event.
That's why you have to fire manually a ContextMenuEvent event, as it was described in this question.
Conclusion: so far, this is basically required only on Android:
TextField textField = new TextField();
addPressAndHoldHandler(textField, Duration.seconds(1), event -> {
Bounds bounds = textField.localToScreen(textField.getBoundsInLocal());
textField.fireEvent(new ContextMenuEvent(ContextMenuEvent.CONTEXT_MENU_REQUESTED,
0, 0, bounds.getMinX() + 10, bounds.getMaxY() + 10, false, null));
});
thanks for checking my question out!
I'm currently working on a project using Qt C++, which is designed to be multi-platform. I'm a bit of a newcoming to it, so I've been asked to set up the ability to take screenshots from within the menu structure, and I'm having issues with the Android version of the companion app.
As a quick overview, it's a bit of software that send the content of a host PC's screen to our app, and I've been able to take screenshots on the Windows version just fine, using QScreen and QPixmap, like so:
overlaywindow.cpp
{
QPixmap screenSnapData = screenGrab->currentBackground();
}
screenGrabber.cpp
{
QScreen *screen = QGuiApplication::primaryScreen();
return screen->grabWindow( QApplication::desktop()->winId() );
}
Unfortunately, Android seems to reject QScreen, and with most suggestions from past Google searches suggesting the now-deprecated QPixmap::grab(), I've gotten a little stuck.
What luck I have had is within the code for the menu itself, and QWidget, but that isn't without issue, of course!
QFile doubleCheckFile("/storage/emulated/0/Pictures/Testing/checking.png");
doubleCheckFile.open(QIODevice::ReadWrite);
QPixmap checkingPixmap = QWidget::grab();
checkingPixmap.save(&doubleCheckFile);
doubleCheckFile.close();
This code does take a screenshot, but only of the button strip currently implemented, and not for the whole screen. I've also taken a 'screenshot' of just a white box with the screen's dimensions by using:
QDesktopWidget dw;
QWidget *screen=dw.screen();
QPixmap checkingPixmap = screen->grab();
Would anyone know of whether there was an alternative to using QScreen to take a screenshot in Android, or whether there's a specific way to get it working as compared to Windows? Or would QWidget be the right track? Any help's greatly appreciated!
as i can read in Qt doc : In your screenGrabber.cpp :
QScreen *screen = QGuiApplication::primaryScreen();
return screen->grabWindow( QApplication::desktop()->winId() );
replace with :
QScreen *screen = QGuiApplication::primaryScreen();
return screen->grabWindow( 0 ); // as 0 is the id of main screen
If you want to take a screenshot of your own widget, you can use the method QWidget::render (Qt Doc):
QPixmap pixmap(widget->size());
widget->render(&pixmap);
If you want to take a screenshot of another app/widget than your app, you should use the Android API...
I'm using Cordova 3.5 to build an app which contains a menu with pretty standard items in the list (home, contacts, etc.), and I want to use the native menu icons whenever possible. I believe those icons are already on the device as part of the OS, but I don't know if Cordova gives me a way to reference them.
I suppose I'd need to write a Javascript function to choose the right file name based on the platform, e.g.:
// this is pseudocode
var icon = '';
if (platform === 'android') {
icon = 'some/path/home.png';
} else {
icon = 'other/path/icon.home.png';
// or maybe a function such as the following exists:
// icon = cordova.getNativeIcon('icon.home.png');
}
$('.selector').css('background-image', icon);
Alternatively, I may be able to make do by referencing the files in CSS, e.g.:
.android .home-icon {
background-image: url('some/path/home.png');
}
.ios .home-icon {
background-image: url('other/path/icon.home.png');
}
So, how do folks handle this sort of thing in Cordova? Is there a function I can use to access native icons? Are folks just copying them into their projects? What's the best practice?
If you're working with Cordova, then you'll be working inside a web view provided by the host OS and you won't have direct access to any artwork. I've found that using icon fonts and CSS "themes" to work well enough, but that approach will replicate artwork already provided. There's extra work involved with theming for iOS 6 vs iOS 7 or 8, for example, but it's not as bad as it sounds.
IBM does have an article on partitioning your view between native and web controls, but it sounds a bit cumbersome. More details here: https://www.ibm.com/developerworks/community/blogs/worklight/entry/ios_combining_native_and_web_controls_in_cordova_based_applications
I am using titanium appecelerator to build an app in both ios and android.
I use the following code to create a tab group and add a tab to it.
var localTabGroup = Ti.UI.createTabGroup();
var planTab = Ti.UI.createTab({
title : NYC.Common.StringConstant.TAB_TITLE_PLAN,
icon : NYC.Common.ResourcePathConstant.IMG_TAB_PLAN,
window : planTabWin
});
localTabGroup.open();
And call the following function to create a window and add it to the tab
addWindowToTabGroup : function(window) {
tabGroup.activeTab.open(window, {
animated : true
});
},
Now, I often have to remove window from the stack of the tab ( eg: on android back button or ios navigation bar back)
Till now, I use window.close() to remove the window from the stack . But, it always shows warnings like.
[ERROR][TiBaseActivity( 378)] (main) [3320,4640528] Layout cleanup.
[WARN][InputManagerService( 62)] Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy#406e4258
I was just wondering if I am following the correct approach? Or is there a better way to remove a window from the tab?
Thanks.
Tabs behave a lot differently on iOS and Android, On Android, the tab does not maintain a stack of windows. Calling open opens a new, heavyweight window, which by default covers the tab group entirely.This is very different from iOS, but it is for Android applications. Users always use the Back button to close the window and return to the tab group.
This may be happening because you are trying to remove the window even though natively Android already removes it. Check out the Android Implementation Notes of the docs here
To completely eliminate this problem, I would just open up a modal window without using the TabGroup, this would be more cross platform:
addWindowToTabGroup : function(window) {
window.open({
modal : true,
animated : true
});
}
This will open a modal window which behaves the same on both platforms, and can be handled easily by the native back button functionality.
I have been trying to create a single codebase for both Iphone & Android for a intermediate level app. ( 4 tabs, multiple windows, maps etc.) using itanium 2.1 API.
However, I have found that things on Android platform dont work as smoothly or willingly as on Iphone epsecially tableviews & UI elemnts. The UI responsiveness on Android is also sluggish.
The kitchen sink examples are pretty straightforward. I am looking at an enterprise ready app which has to be maintained for atleast next couple of years.
Has anybody worked on similar lines with platform quirks and been successful in creating fully functional iOS & Android apps from SAME codebase?
I'm having a lot of success using the compile-time CommonJS mechanism for having a root view that then has os-specific capabilities.
For instance, my os-independent view might be ui/MyView.js :
var createAddButton = require("ui/MyView.AddButton");
var MyView = function() {
var self = Ti.UI.createWindow();
createAddButton(self, function() { alert('ADD!'); });
return self;
};
module.exports = MyView;
Then, I create os-specific functions to handle it:
iphone/ui/MyView.AddButton.js
module.exports = function(view, addHandler) {
var addButton = Titanium.UI.createButton({
systemButton: Titanium.UI.iPhone.SystemButton.ADD
});
addButton.addEventListener("click", addHandler);
view.rightNavButton = addButton;
};
android/ui/MyView.AddButton.js
module.exports = function(view, addHandler) {
view.activity.onCreateOptionsMenu = function(e){
var menuItem = e.menu.add({ title: "Add" });
menuItem.addEventListener("click", addHandler);
};
};
The CommonJS system they have implemented will pick the appropriate version of MyView.AddButton.js so that the button is added to the right place. It allows for the majority of the view to be the same, but the os-specific things to be separated properly.
Titanium is not meant for 1 codebase for all. You do need to rewrite stuff for every OS. However, some app developers claim to have reused 95% of its code. So only 5% of the code is OS specific. But I am sure their code is full with if-elses.
What I recommend doing, to be able to maintain it properly, without thousands of if-else constructions, is build a single backend core, and write code specifically for UI related matters per OS. This way, you have some UI related code for Android, UI related code for iOS and 1 core working for both.
Since Android and iOS differ a lot, writing a single codebase will make sure you can never use OS specific features (like android hardware menu button, or iOS NavigationGroup), and will let the UI look non-intuitive.