Skip to content

iOS: Fixed Toolbar and North Container Become Blank While Scrolling a Separate Center Container #5273

Description

@JNorth83

Title

iOS: Fixed Toolbar and North Container Become Blank While Scrolling a Separate Center Container

Description

On a physical iOS device, scrolling a container placed in the BorderLayout.CENTER position can cause the fixed content above it to stop painting.

The affected area includes:

  • The Form toolbar and title
  • Back and overflow menu icons
  • A non-scrollable container in BorderLayout.NORTH

The components are still present and interactive. For example, the invisible back button and overflow menu button can still be pressed. Opening the overflow menu forces the missing toolbar and North container to repaint and become visible again.

The issue does not occur on:

  • Android devices
  • The Codename One simulator using Android or iOS skins

This behaviour was not present in previous versions of the application/Codename One.

Expected Behaviour

The toolbar and North container should remain visible while the independently scrollable Center container is scrolled.

Actual Behaviour

During or after scrolling:

  • The toolbar becomes blank.
  • The North container becomes blank.
  • The components remain clickable.
  • Opening the overflow menu causes the fixed areas to reappear.

Original Application Structure

The original page uses:

Form form = new Form("Advanced Settings", new BorderLayout());

Container north = new Container(BoxLayout.y());

Container center = new Container(BoxLayout.y());
center.setScrollableY(true);

form.add(BorderLayout.NORTH, north);
form.add(BorderLayout.CENTER, center);

The Form content pane itself is not scrollable. Only the Center container scrolls.

The North container originally contained an image and a label. The reduced test case replaces the image with a simple fixed-height coloured component.

Test Case

public void showIosRepaintTestForm(Form mainForm) {
Form form = new Form("iOS Repaint Test", new BorderLayout());

form.getContentPane().setScrollableY(false);
form.getContentPane().setSafeArea(true);

form.getToolbar().setBackCommand("", e -> mainForm.showBack());
form.getToolbar().addCommandToOverflowMenu(
        "Option 1",
        null,
        e -> Log.p("Option 1")
);
form.getToolbar().addCommandToOverflowMenu(
        "Option 2",
        null,
        e -> Log.p("Option 2")
);

/*
 * 0: No manual repaint
 * 1: Repaint North
 * 2: Repaint title area and North
 * 3: Repaint the complete fixed header region
 */
final int[] repaintMode = {0};

String[] modeDescriptions = {
    "No manual repaint",
    "Repaint North only",
    "Repaint title and North",
    "Repaint fixed header region"
};

/*
 * Fixed North container.
 */
Container north = new Container(BoxLayout.y());

Label colourBlock = new Label("FIXED NORTH AREA");
colourBlock.setAlignment(Component.CENTER);
colourBlock.setPreferredH(CN.convertToPixels(25));

colourBlock.getAllStyles().setBgColor(0x1976D2);
colourBlock.getAllStyles().setBgTransparency(255);
colourBlock.getAllStyles().setFgColor(0xFFFFFF);

Button modeButton = new Button(
        "Mode 0: " + modeDescriptions[0]
);

modeButton.addActionListener(e -> {
    repaintMode[0] = (repaintMode[0] + 1) % 4;

    modeButton.setText(
            "Mode " + repaintMode[0] + ": "
                    + modeDescriptions[repaintMode[0]]
    );

    Log.p("Repaint mode: " + repaintMode[0]);
    north.revalidate();
});

north.add(colourBlock);
north.add(modeButton);

/*
 * Independently scrollable Center container.
 */
Container center = new Container(BoxLayout.y());
center.setScrollableY(true);
center.setScrollVisible(false);

for (int i = 1; i <= 60; i++) {
    final int rowNumber = i;

    Button row = new Button("Scrollable item " + i);
    row.addActionListener(e ->
            Log.p("Pressed item " + rowNumber)
    );

    center.add(row);
}

form.add(BorderLayout.NORTH, north);
form.add(BorderLayout.CENTER, center);

center.addScrollListener(
        (scrollX, scrollY, oldScrollX, oldScrollY) -> {
            switch (repaintMode[0]) {
                case 1:
                    north.repaint();
                    break;

                case 2:
                    form.getTitleArea().repaint();
                    north.repaint();
                    break;

                case 3:
                    form.repaint(
                            0,
                            0,
                            form.getWidth(),
                            center.getAbsoluteY()
                    );
                    break;

                default:
                    // Mode 0: no manual repaint.
                    break;
            }
        }
);

form.show();

}

  • A standard Codename One toolbar
  • A back command
  • Two overflow menu commands
  • A fixed coloured block in BorderLayout.NORTH
  • A long independently scrollable list in BorderLayout.CENTER
  • A button allowing four repaint modes to be selected

The modes are:

  • Mode 0: No manual repaint
  • Mode 1: Repaint the North container
  • Mode 2: Repaint the Form title area and North container
  • Mode 3: Repaint the continuous screen region above the Center container

Mode 0 represents the page without a workaround.

Mode 1 was tested because repainting the North container restores it, but the toolbar may remain blank.

Mode 2 calls:

form.getTitleArea().repaint();
north.repaint();

This successfully keeps both fixed areas visible in the test case however cause a flicker in the fixed areas.

Mode 3 repaints the complete fixed header region using:

form.repaint(
    0,
    0,
    form.getWidth(),
    center.getAbsoluteY()
);

Steps to Reproduce

  1. Build and install the test application on a physical iOS device.
  2. Open the iOS repaint test page.
  3. Leave the page in Mode 0.
  4. Repeatedly scroll the Center list up and down.
  5. Watch the toolbar and fixed coloured North area.
  6. If they become blank, press the location where the overflow menu icon should be.
  7. Observe that the overflow menu opens and the missing fixed content is repainted.
  8. Repeat the test using Modes 1, 2 and 3 for comparison.

The issue may be intermittent, so scrolling up and down several times may be required. Also note mode 2 will appear to resolve the issue however repeated scrolling can cause a visible flicker in the fixed areas.

Workaround Tested

Adding a scroll listener and repainting the fixed components reduces or prevents the problem:

center.addScrollListener((scrollX, scrollY, oldX, oldY) -> {
    form.getTitleArea().repaint();
    north.repaint();
});

However, using manual repaint calls during scrolling can produce occasional flickering in the original, more complex page. Therefore, this does not appear to be an ideal production solution.

Additional Observations

  • The missing components continue to receive pointer events.
  • No components are deliberately hidden or removed while scrolling.
  • Opening the overflow menu consistently triggers a successful repaint.
  • Repainting only the North container does not always restore the toolbar.
  • Repainting the complete title area and North container works in the test case, however causes a flicker on the fixed areas.
  • The attached video demonstrates the behaviour and compares the available repaint modes.
Video.mov

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions