I have a Xamarin.Forms app with FreshMvvm, and I use secondary ToolbarItems. To do it in iOS, I had to make a custom renderer (unlike in Android). I was given a solution on how to implement it here:
ToolbarItems do not look right in iOS
This solution works perfectly for me. But by now, the toolbar menu grew longer, and some of its elements do not fit the iPhone's screen. I can slide the menu and see all the elements, but as soon as I release the screen, the view jumps back up, and while it is held by a finger, the elements are not clickable. How can this be solved? Can the menu made to wrap, or something else?
On Android, the menu stays where I scroll and I can click every item. Can it made stay where scrolled on iOS, too?
Here is my renderer's code:
using CoreGraphics;
using MobileApp.iOS.Renderers;
using MobileApp.iOS.Services;
using MobileApp.Pages;
using MobileApp.Services;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(CustomToolbarContentPage),
typeof(RightToolbarMenuCustomRenderer))]
namespace MobileApp.iOS.Renderers
{
class RightToolbarMenuCustomRenderer : PageRenderer
{
private List<ToolbarItem> _primaryItems;
private List<ToolbarItem> _secondaryItems;
private UITableView _table;
private UITapGestureRecognizer _tapGestureRecognizer;
private UIView _transparentView;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
if (e.NewElement is IAddToolbarItem item)
{
item.ToolbarItemAdded += Item_ToolbarItemAdded;
}
base.OnElementChanged(e);
}
private void Item_ToolbarItemAdded(object sender, System.EventArgs e)
{
if (Element is ContentPage page)
{
_primaryItems = page.ToolbarItems.Where(i => i.Order == ToolbarItemOrder.Primary).ToList();
_secondaryItems = page.ToolbarItems.Where(i => i.Order == ToolbarItemOrder.Secondary).ToList();
_secondaryItems.ForEach(t => page.ToolbarItems.Remove(t));
}
var element = (ContentPage)Element;
if (_secondaryItems?.Count == 0 && element.ToolbarItems.Any(a => (a.IconImageSource as FileImageSource)?.File == "more.png"))
{
element.ToolbarItems.Clear();
}
else if (_secondaryItems?.Count >= 1 && !element.ToolbarItems.Any(a => (a.IconImageSource as FileImageSource)?.File == "more.png"))
{
element.ToolbarItems.Add(new ToolbarItem()
{
Order = ToolbarItemOrder.Primary,
IconImageSource = "more.png",
Priority = 1,
Command = new Command(ToggleDropDownMenuVisibility)
});
}
}
private void ToggleDropDownMenuVisibility()
{
if (!DoesTableExist())
{
if ((View?.Subviews != null)
&& (View.Subviews.Length > 0)
&& (View.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
_table = OpenDropDownMenu(Element as IAddToolbarItem);
Add(_table);
}
}
else
CloseDropDownMenu();
}
private bool DoesTableExist()
{
if (View?.Subviews != null)
{
foreach (var subview in View.Subviews)
{
if (_table != null && subview == _table)
{
return true;
}
}
}
if (_tapGestureRecognizer != null)
{
_transparentView?.RemoveGestureRecognizer(_tapGestureRecognizer);
_tapGestureRecognizer = null;
}
_table = null;
_tapGestureRecognizer = null;
return false;
}
private UITableView OpenDropDownMenu(IAddToolbarItem secondaryMenuSupport)
{
_transparentView = _transparentView = new UIView(new CGRect(0, 0, View.Bounds.Width, View.Bounds.Height))
{
BackgroundColor = UIColor.FromRGBA(0, 0, 0, 0)
};
_tapGestureRecognizer = new UITapGestureRecognizer(CloseDropDownMenu);
_transparentView.AddGestureRecognizer(_tapGestureRecognizer);
Add(_transparentView);
UITableView table = null;
if (_secondaryItems != null && _secondaryItems.Count > 0)
{
table = new UITableView(GetPositionForDropDownMenu(secondaryMenuSupport.RowHeight, secondaryMenuSupport.TableWidth))
{
Source = new TableSource(_secondaryItems, _transparentView),
ClipsToBounds = false
};
table.ScrollEnabled = true;
table.Layer.ShadowColor = secondaryMenuSupport.ShadowColor.ToCGColor();
table.Layer.ShadowOpacity = secondaryMenuSupport.ShadowOpacity;
table.Layer.ShadowRadius = secondaryMenuSupport.ShadowRadius;
table.Layer.ShadowOffset = new SizeF(secondaryMenuSupport.ShadowOffsetDimension, secondaryMenuSupport.ShadowOffsetDimension);
table.BackgroundColor = secondaryMenuSupport.MenuBackgroundColor.ToUIColor();
}
return table;
}
public override void ViewWillDisappear(bool animated)
{
CloseDropDownMenu();
base.ViewWillDisappear(animated);
}
private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
return new RectangleF(
(float)View.Bounds.Width - tableWidth,
0,
tableWidth,
_secondaryItems.Count() * rowHeight);
}
else
{
return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
}
}
private void CloseDropDownMenu()
{
if (_table != null)
{
if (_tapGestureRecognizer != null)
{
_transparentView?.RemoveGestureRecognizer(_tapGestureRecognizer);
_tapGestureRecognizer = null;
}
if (View?.Subviews != null)
{
foreach (var subview in View.Subviews)
{
if (subview == _table)
{
_table.RemoveFromSuperview();
break;
}
}
if (_transparentView != null)
{
foreach (var subview in View.Subviews)
{
if (subview == _transparentView)
{
_transparentView.RemoveFromSuperview();
break;
}
}
}
}
_table = null;
_transparentView = null;
}
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
if (_table != null)
{
if (Element is IAddToolbarItem secondaryMenuSupport)
PositionExistingDropDownMenu(secondaryMenuSupport.RowHeight, secondaryMenuSupport.TableWidth);
}
}
private void PositionExistingDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0)
&& (_table != null))
{
_table.Frame = GetPositionForDropDownMenu(rowHeight, tableWidth);
}
}
}
}
ADDITION:
public class TableSource : UITableViewSource
{
List<ToolbarItem> _tableItems;
string[] _tableItemTexts;
string CellIdentifier = "TableCell";
UIView _tableSuperView = null;
public TableSource(List<ToolbarItem> items, UIView tableSuperView)
{
_tableItems = items;
_tableSuperView = tableSuperView;
_tableItemTexts = items.Select(a => a.Text).ToArray();
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return _tableItemTexts?.Length ?? 0;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell(CellIdentifier);
string item = _tableItemTexts[indexPath.Row];
if (cell == null)
{ cell = new UITableViewCell(UITableViewCellStyle.Default, CellIdentifier); }
cell.TextLabel.Text = item;
return cell;
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
return 56;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
var command = _tableItems[indexPath.Row].Command;
command.Execute(_tableItems[indexPath.Row].CommandParameter);
tableView.DeselectRow(indexPath, true);
tableView.RemoveFromSuperview();
if (_tableSuperView != null)
{
_tableSuperView.RemoveFromSuperview();
}
}
}
public interface IAddToolbarItem
{
event EventHandler ToolbarItemAdded;
Color CellBackgroundColor { get; }
Color CellTextColor { get; }
Color MenuBackgroundColor { get; }
float RowHeight { get; }
Color ShadowColor { get; }
float ShadowOpacity { get; }
float ShadowRadius { get; }
float ShadowOffsetDimension { get; }
float TableWidth { get; }
}
Here you can download the project that reproduces the issue:
https://github.com/DavidShochet/Public
Well, I still can't reproduce the problem with your updated code. I don't if it is a solution and I just want to clarify my comment here.
In your code, you add the _table to the View:
_table = OpenDropDownMenu(Element as IAddToolbarItem);
Add(_table);
What I want you to have a try is add the _table to _transparentView :
_table = OpenDropDownMenu(Element as IAddToolbarItem);
//Add(_table);
_transparentView.Add(_table);
It would be better if you can provide us a Minimal, Reproducible Example so that I can debug it on my side.
Update:
I found the problem is here, you set the height of table _secondaryItems.Count() * rowHeight) which is longer then the view when the toolbar menu grew longer:
private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
return new RectangleF(
(float)View.Bounds.Width - tableWidth,
0,
tableWidth,
//here is the cause
_secondaryItems.Count() * rowHeight);
}
else
{
return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
}
}
Solution: change the height of tablview to (float)View.Bounds.Height:
private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
{
if ((View?.Bounds != null)
&& (_secondaryItems != null)
&& (_secondaryItems.Count > 0))
{
return new RectangleF(
(float)View.Bounds.Width - tableWidth,
0,
tableWidth,
(float)View.Bounds.Height);
}
else
{
return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
}
}
I'm trying to make an autocomplete combobox in javafx and im almost done but every time i try to submit and clear selection so the user can choose another choice , all the items get hidden except the one he chose last time
// this the class that i used turn a normal combobox to an autocomplete one
public class AutoCompleteComboBoxListener<T> implements EventHandler<KeyEvent> {
private ComboBox<T> comboBox;
private ObservableList<T> data;
private boolean moveCaretToPos = false;
private int caretPos;
public AutoCompleteComboBoxListener(final ComboBox<T> comboBox) {
this.comboBox = comboBox;
data = comboBox.getItems();
this.comboBox.setEditable(true);
this.comboBox.setOnKeyReleased(AutoCompleteComboBoxListener.this);
this.showOnFocus();
}
#Override
public void handle(KeyEvent event) {
if(event.getCode() == KeyCode.UP) {
caretPos = -1;
moveCaret(comboBox.getEditor().getText().length());
return;
} else if(event.getCode() == KeyCode.DOWN) {
if(!comboBox.isShowing())
comboBox.show();
caretPos = -1;
moveCaret(comboBox.getEditor().getText().length());
return;
}
if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.LEFT
|| event.isControlDown() || event.getCode() == KeyCode.HOME
|| event.getCode() == KeyCode.END || event.getCode() == KeyCode.TAB) {
return;
}
System.out.println(caretPos);
comboBox.hide();
if(event.getCode() == KeyCode.BACK_SPACE) {
moveCaretToPos = true;
caretPos = comboBox.getEditor().getCaretPosition();
} else if(event.getCode() == KeyCode.DELETE) {
moveCaretToPos = true;
caretPos = comboBox.getEditor().getCaretPosition();
}
ObservableList<T> list = FXCollections.observableArrayList();
for (int i=0; i<data.size(); i++) {
if(data.get(i).toString().toLowerCase().startsWith(
AutoCompleteComboBoxListener.this.comboBox
.getEditor().getText().toLowerCase())) {
list.add(data.get(i));
}
}
String t = comboBox.getEditor().getText();
comboBox.setItems(list);
comboBox.getEditor().setText(t);
if(!moveCaretToPos) {
caretPos = -1;
}
moveCaret(t.length());
if(!list.isEmpty()) {
comboBox.show();
String editorText;
editorText=comboBox.getEditor().getText();
list.sort((a, b) -> Integer.compare(a.toString().length(), b.toString().length()));
comboBox.getSelectionModel().selectFirst();
comboBox.getEditor().setText(editorText);
moveCaret(comboBox.getSelectionModel().getSelectedItem().toString().length());
}
if (event.getCode()==KeyCode.ENTER){
comboBox.hide();
comboBox.getEditor().setText(comboBox.getSelectionModel().getSelectedItem().toString());
}
}
private void moveCaret(int textLength) {
if(caretPos == -1)
comboBox.getEditor().positionCaret(textLength);
else
comboBox.getEditor().positionCaret(caretPos);
moveCaretToPos = false;
}
public void showOnFocus(){
this.comboBox.focusedProperty().addListener((obs, oldVal, newVal) ->
this.comboBox.show());
}
}
// and this is what the submit button does the the comboboxes
button.setOnAction(e-> {
combo1.getSelectionModel().clearSelection();
combo1.getSelectionModel().clearSelection();
});
I have cursor returned by an SQLite query, I would like to know correct approach for creating an Observable the emits each row in the cursor.
I created cursor observable as follows, please check if this is the correct:
Observable<Cursor> cursorObservable = Observable.create(new ObservableOnSubscribe<Cursor>() {
#Override
public void subscribe(ObservableEmitter<Cursor> e) throws Exception {
SQLDbHelper dbHelper = SQLDbHelper.getInstance(ctx);
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("select * from " + MoviesContract.MovieEntry.TABLE_NAME, null);
if (cursor != null) {
try {
while (cursor.moveToNext() && !e.isDisposed()) {
e.onNext(cursor);
}
} catch (Exception exception) {
e.onError(exception);
} finally {
cursor.close();
}
}
if (!e.isDisposed()) {
e.onComplete();
}
}
});
I thank that you will have better results wrapping the rows into a Map and passing it through the stream than passing cursor itself.
class SimpleTest {
#Test
fun testCursorStream() {
val cursor = fakeCursor()
val stream = getCursorStream(cursor)
stream.subscribe {
Log.d("Test", it.entries.toString())
}
}
private fun fakeCursor() : Cursor {
val columns = arrayOf("id", "name", "age")
val cursor = MatrixCursor(columns)
val row1 = arrayOf(1, "Rodrigo", 26L)
val row2 = arrayOf(2, "Lucas", 23L)
val row3 = arrayOf(3, "Alan", 26L)
cursor.addRow(row1)
cursor.addRow(row2)
cursor.addRow(row3)
return cursor
}
private fun getCursorStream(cursor: Cursor) : Observable<Map<String, Any?>> {
return Observable.create<Map<String, Any?>> {
try {
if (!cursor.moveToFirst()) {
it.onCompleted()
return#create
}
val row = HashMap<String, Any?>()
do {
val lastColumnIndex = cursor.columnCount - 1
for (index in 0..lastColumnIndex) {
val name = cursor.getColumnName(index)
val type = cursor.getType(index)
when (type) {
Cursor.FIELD_TYPE_STRING -> row.put(name, cursor.getString(index))
Cursor.FIELD_TYPE_BLOB -> row.put(name, cursor.getBlob(index))
Cursor.FIELD_TYPE_FLOAT -> row.put(name, cursor.getFloat(index))
Cursor.FIELD_TYPE_INTEGER -> row.put(name, cursor.getInt(index))
Cursor.FIELD_TYPE_NULL -> row.put(name, null)
}
}
it.onNext(row)
} while (cursor.moveToNext())
it.onCompleted()
} catch (e: Exception) {
it.onError(e)
}
}
}
}
Hope that it helps.
I am stuck on a complex situation, when I am refreshing my adapter of recyclerview by notifyDataSetChanged, while it having focus on particular cell (with D-PAD), is being loss. After refresh focus is not showing
private void refreshedData() {
flag1 = true;
// new MyAsycTas().execute();
focusedElement();
// select();
runnable = new Runnable() {
#Override
public void run() {
Log.e(TAG, "Data refreshed starrt.......");
// isRefreshedData = true;
fetchXmlFromUrl = new FetchDataFromUrl(Demo2Activity.this, 0, Demo2Activity.this);
handler.removeCallbacks(runnable);
select();
handler.postDelayed(runnable, 9000);
}
};
// handler.postDelayed(runnable,
// Integer.valueOf(MyStaticClass.refreshTime) * 60000);
handler.postDelayed(runnable, 9000);
}
...
public void select() {
if (verticalListView != null) {
View view = verticalListView.getChildAt(vertical_position);
if (view != null) {
view = verticalListView.getChildAt(vertical_position);
recyclerView = (RecyclerView) view.findViewById(R.id.horizontal_recyclerview);
if (recyclerView != null) {
verticalListView.scrollToPosition(vertical_position);
((View) recyclerView.getChildAt(horizontal_position)).findViewById(R.id.cat_button_thumabnail)
.requestFocus();
} else {
LinearLayout linearLayout = (LinearLayout) view.findViewById(R.id.home_fragment_menu_container);
linearLayout.getChildAt(horizontal_position).requestFocus();
}
}
}
// refreshedData();
}
..
public void focusedElement() {
if (verticalListView != null) {
View view = verticalListView.getLayoutManager().getFocusedChild();
if (view != null) {
recyclerView = (RecyclerView) view.findViewById(R.id.horizontal_recyclerview);
if (recyclerView != null) {
vertical_position = (Integer) recyclerView.getTag();
horizontal_position = recyclerView.getChildAdapterPosition(recyclerView.getFocusedChild());
} else {
LinearLayout ll = (LinearLayout) view.findViewById(R.id.home_fragment_menu_container);
horizontal_position = ll.indexOfChild(ll.getFocusedChild());
}
}
}
}
I have a TextArea with a text in which I want to search for word(s). The search works, but highlighting the word with selectRange() does not. Is there a different method for highlighting?
findButton.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (textField.getText() != null && !textField.getText().isEmpty()) {
int index = textArea.getText().indexOf(textField.getText());
if (index == -1) {
errorText.setText("Search key Not in the text");
} else {
// errorText.setText("Found");
textArea.selectRange(textField.getText().charAt(0), textField.getLength());
}
} else {
errorText.setText("Missing search key");
// errorText.setFill(Color.RED);
}
}
});
Surely you mean
textArea.selectRange(index, index + textField.getLength());