PyInstaller macOS issue with QCombobox not refreshing

Fixing widget redraw problems in PyQt5 apps packaged with PyInstaller on macOS
Heads up! You've already completed this tutorial.

If you've packaged a PyQt5 application with PyInstaller on macOS and found that your QComboBox (or other widgets) aren't visually updating, this is a known issue that tends to surface on certain macOS versions and Python/Qt environment combinations. The widgets do receive their data — but the GUI simply doesn't redraw to show it.

In this article, we'll walk through the problem, explain why it happens, and cover several practical fixes you can apply right away.

Reported Problems

You have code like this that populates a QComboBox:

python
connections = SerialPorts()
ports = connections.available()

self.pick_serial_ports.clear()
self.pick_serial_ports.addItems(ports)
self.pick_serial_ports.setCurrentIndex(0)
self.pick_serial_ports.update()

When you run the script directly with Python, everything works fine — the combo box shows the first available serial port. But after packaging with PyInstaller and running the .app bundle on macOS, the combo box appears blank. If you minimize the window and restore it, or click away and back, the correct value suddenly appears.

It's as though QApplication.exec() isn't processing paint events properly inside the packaged app.

Why does this happen?

This issue is related to how macOS handles event processing for application bundles. When PyInstaller packages your app, it creates a macOS .app bundle that runs your Python code inside a wrapper. Depending on the macOS version and the version of Qt being used, the app may not be recognized as a proper "foreground" application by the operating system. When that happens, macOS can delay or suppress GUI redraw events.

The problem became particularly widespread with macOS Catalina (10.15) and Big Sur (11.0), where Apple made significant changes to how apps are rendered and given focus.

Fix 1: Force the app to process events

The most direct workaround is to explicitly tell Qt to process any pending events after making your widget changes. You can do this with QApplication.processEvents():

python
from PyQt5.QtWidgets import QApplication

connections = SerialPorts()
ports = connections.available()

self.pick_serial_ports.clear()
self.pick_serial_ports.addItems(ports)
self.pick_serial_ports.setCurrentIndex(0)

# Force Qt to process pending paint events
QApplication.processEvents()

This nudges the event loop to handle any queued-up drawing operations immediately, rather than waiting for the OS to trigger them.

Packaging Python Applications with PyInstaller by Martin Fitzpatrick — This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.

More info Get the book

Fix 2: Use a short delay with QTimer

Sometimes processEvents() alone isn't enough, because the macOS window server hasn't yet registered the app as active. Introducing a tiny delay before updating widgets can help:

python
from PyQt5.QtCore import QTimer

def populate_ports(self):
    connections = SerialPorts()
    ports = connections.available()

    self.pick_serial_ports.clear()
    self.pick_serial_ports.addItems(ports)
    self.pick_serial_ports.setCurrentIndex(0)

# Delay the population slightly to let the event loop settle
QTimer.singleShot(100, self.populate_ports)

By deferring the widget update by 100 milliseconds, you give the application's event loop time to fully initialize and start processing draw events normally.

Fix 3: Ensure the app is a proper foreground application

On macOS, an application needs to be recognized as a "foreground" app to receive proper drawing and focus events. PyInstaller bundles sometimes aren't recognized this way. You can force it using the AppKit framework via the pyobjc package.

First, install pyobjc-framework-Cocoa:

sh
pip install pyobjc-framework-Cocoa

Then, at the very beginning of your application (before creating QApplication), add:

python
import sys

# macOS-specific fix for foreground app status
if sys.platform == "darwin":
    try:
        from AppKit import NSApplication, NSApp
        NSApplication.sharedApplication()
        NSApp.setActivationPolicy_(0)  # NSApplicationActivationPolicyRegular
    except ImportError:
        pass

The activation policy value 0 corresponds to NSApplicationActivationPolicyRegular, which tells macOS to treat the process as a full GUI application with a dock icon and proper event handling. This often resolves redraw issues entirely.

Make sure to include pyobjc-framework-Cocoa in your PyInstaller build so it gets bundled with the app. You can add it as a hidden import:

sh
pyinstaller --hidden-import=AppKit your_script.py

Fix 4: Use the correct PyInstaller options for macOS

How you invoke PyInstaller matters on macOS. If you're building with --onefile or --windowed (also called --noconsole), the resulting app bundle behaves differently than a plain terminal-launched script.

For GUI apps on macOS, always use --windowed:

sh
pyinstaller --windowed --name "MyApp" your_script.py

The --windowed flag tells PyInstaller to create a proper .app bundle and suppresses the terminal window. Without it, your app may launch as a background process that doesn't receive focus or redraw events correctly.

If you're already using --windowed and still seeing the issue, try combining it with Fix 3 above.

Fix 5: Pin compatible versions of PyQt5 and PyInstaller

Environment mismatches are a common source of packaging bugs. Different versions of PyQt5, Qt, and PyInstaller can interact in unexpected ways — especially on macOS, where Apple regularly changes how apps are rendered.

Here's a combination that has been known to work reliably:

sh
pip install PyQt5==5.15.4
pip install PyInstaller==4.5.1

If you're using Anaconda, be especially careful. Conda can install its own builds of Qt that may conflict with pip-installed PyQt5. If you're experiencing issues in a Conda environment, try creating a clean virtual environment with venv instead:

sh
python3 -m venv myenv
source myenv/bin/activate
pip install PyQt5 PyInstaller

This gives you a clean, predictable set of packages without Conda's extra layers.

Fix 6: Handle macOS Big Sur specifically

macOS Big Sur (11.0) introduced further rendering changes that broke many packaged PyQt5 apps. Widgets might not render at all — not just a delayed redraw, but a completely blank window. For more details on Big Sur-specific configuration issues, see macOS Big Sur config to open windows with PyQt5 or PySide2.

The root cause was a change in how Big Sur handles OpenGL and layer-backed views. The fix is to set an environment variable that forces Qt to use a compatible rendering path. Add this to the very top of your script, before any Qt imports:

python
import os
os.environ["QT_MAC_WANTS_LAYER"] = "1"

This tells Qt to use layer-backed rendering, which is what Big Sur expects. Without it, Qt may silently fail to draw widgets.

Your script's opening lines should look like:

python
import os
import sys

os.environ["QT_MAC_WANTS_LAYER"] = "1"

from PyQt5.QtWidgets import QApplication, QMainWindow

The environment variable must be set before importing any PyQt5 modules, because Qt reads it during initialization.

Putting it all together

Here's a complete minimal example that incorporates the most important fixes. You can use this as a starting template for any PyQt5 app you plan to package with PyInstaller on macOS:

python
import os
import sys

# Must be set before importing PyQt5 — fixes Big Sur rendering
os.environ["QT_MAC_WANTS_LAYER"] = "1"

# Ensure the app is treated as a foreground GUI app on macOS
if sys.platform == "darwin":
    try:
        from AppKit import NSApplication, NSApp
        NSApplication.sharedApplication()
        NSApp.setActivationPolicy_(0)
    except ImportError:
        pass

from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import (
    QApplication,
    QComboBox,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Serial Port Selector")

        layout = QVBoxLayout()

        self.pick_serial_ports = QComboBox()
        layout.addWidget(self.pick_serial_ports)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        # Defer widget population slightly to ensure the event loop is running
        QTimer.singleShot(100, self.populate_ports)

    def populate_ports(self):
        # Replace this with your actual serial port discovery
        ports = ["COM1", "COM2", "/dev/ttyUSB0"]

        self.pick_serial_ports.clear()
        self.pick_serial_ports.addItems(ports)
        self.pick_serial_ports.setCurrentIndex(0)

        # Force event processing to ensure the widget redraws
        QApplication.processEvents()


def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

To package this with PyInstaller:

sh
pyinstaller --windowed --name "SerialSelector" main.py

If you're using the AppKit fix, add the hidden import:

sh
pyinstaller --windowed --hidden-import=AppKit --name "SerialSelector" main.py

If you're also experiencing general frustrations with PyInstaller on macOS, check our dedicated troubleshooting guide for additional tips.

Quick reference: which fix to try first

Symptom Recommended fix
Widgets update after clicking away and back Fix 1 (processEvents) or Fix 2 (QTimer)
App doesn't seem to get focus when launched Fix 3 (AppKit activation policy)
Completely blank window on Big Sur Fix 6 (QT_MAC_WANTS_LAYER)
Works in one Conda env but not another Fix 5 (clean venv with pinned versions)
Works from terminal but not as .app bundle Fix 4 (--windowed flag)

These issues can be frustrating, especially when everything works perfectly during development and only fails after packaging. The good news is that with the right combination of environment variables, activation policies, and event processing, you can get reliable widget rendering across macOS versions. Start with the simplest fix that matches your symptom and layer in additional fixes if needed.

PyQt/PySide Development Services — Stuck in development hell? I'll help you get your project focused, finished and released. Benefit from years of practical experience releasing software with Python.

Find out More

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Create GUI Applications with Python & Qt6 by Martin Fitzpatrick

(PyQt6 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!

More info Get the book

Martin Fitzpatrick

PyInstaller macOS issue with QCombobox not refreshing was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.