Fixing PyQt Redraw Issues on macOS

Solving display and rendering glitches when running PyQt applications on macOS
Heads up! You've already completed this tutorial.

I'm running PyQt5 on macOS Mojave and having redraw issues. Labels, text fields, and images don't update on events unless I click on another window (like my IDE or terminal). When clearing text in a field, it half-clears, leaving pixel fragments. The updated values are being passed correctly (I can see them via print statements), but the display doesn't refresh. What's going on?

This is a well-known issue that affects PyQt applications on certain versions of macOS. The good news is that there are straightforward solutions. Let's walk through what causes this and how to fix it.

Why Does This Happen?

macOS has its own rendering system for drawing windows and widgets on screen. Sometimes, PyQt and the macOS display layer get out of sync—your code updates a widget's value, but macOS doesn't know it needs to repaint that area of the window. The result is visual artifacts: half-drawn text, stale labels, or widgets that only look correct after you switch to another application and back.

This problem has been especially common on macOS Mojave (10.14) and some later versions, particularly when using certain versions of Qt.

Force a Repaint with update() or repaint()

If a widget isn't redrawing after you change its content, you can ask it to refresh explicitly. Every Qt widget has two methods for this:

  • .update() — schedules a repaint. Qt will repaint the widget the next time it processes events. This is the preferred approach in most cases.
  • .repaint() — forces an immediate repaint. Use this only if .update() isn't enough.

For example, if you're updating a QLabel and the display isn't changing:

python
self.label.setText("New value")
self.label.update()

Or, if the parent widget or layout needs to refresh:

python
self.label.setText("New value")
self.centralWidget().update()

Process Pending Events

Sometimes the issue is that Qt's event loop hasn't had a chance to process the repaint. If you're doing work in a slot (a function connected to a signal), the event loop is paused until that function returns. Calling QApplication.processEvents() gives Qt a moment to handle pending repaints:

python
from PyQt6.QtWidgets import QApplication

self.label.setText("Updated!")
QApplication.processEvents()

Use this sparingly—it's a helpful workaround, but if you find yourself needing it frequently, it may be a sign that long-running work should be moved to a background thread.

Over 15,000 developers have bought Create GUI Applications with Python & Qt!
Create GUI Applications with Python & Qt6
Get the book

Downloadable ebook (PDF, ePub) & Complete Source code

[[ discount.discount_pc ]]% OFF for the next [[ discount.duration ]] [[discount.description ]] with the code [[ discount.coupon_code ]]

Purchasing Power Parity

Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses with code [[ discount.coupon_code ]]

Set the QT_MAC_WANTS_LAYER Environment Variable

For macOS Mojave specifically, a common and effective fix is to set the environment variable QT_MAC_WANTS_LAYER to 1. This tells Qt to use Core Animation layers for rendering, which resolves many display glitches on macOS.

You can set this at the very top of your script, before creating your QApplication:

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

Or, you can set it in your terminal before running your script:

sh
export QT_MAC_WANTS_LAYER=1
python my_app.py

This single change fixes the problem for many people on macOS Mojave. In later versions of Qt (Qt 6 / PyQt6), this behavior is enabled by default, so you may not need it if you upgrade.

Upgrade Qt and PyQt

Many of the macOS rendering bugs were fixed in newer versions of Qt. If you're currently using PyQt5 with an older Qt version, consider:

  1. Updating PyQt5 and its dependencies to the latest available versions:

    sh pip install --upgrade PyQt5

  2. Switching to PyQt6, which uses Qt 6 and includes many macOS-specific rendering fixes:

    sh pip install PyQt6

    PyQt6 works very similarly to PyQt5. Most code transfers over with only small changes (such as how enums are accessed). If you're starting out, building on PyQt6 from the beginning will save you from running into these older platform bugs.

Upgrade macOS

Some of these rendering issues were resolved in macOS updates after Mojave. If upgrading your operating system is an option, moving to macOS Catalina (10.15) or later can help. Combined with an updated version of Qt, this eliminates most of the redraw problems.

Complete Working Example

Here's a small application that updates a label and an image on a button press. It includes the QT_MAC_WANTS_LAYER fix and an explicit .update() call, so it should work correctly even on macOS Mojave:

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

import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Redraw Test")

        self.label = QLabel("Click the button to update this text.")
        self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)

        self.image_label = QLabel()
        self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.image_label.setFixedSize(200, 200)
        self.image_label.setStyleSheet("background-color: #ddd;")

        self.button = QPushButton("Update")
        self.button.clicked.connect(self.update_display)

        self.click_count = 0

        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.image_label)
        layout.addWidget(self.button)

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

    def update_display(self):
        self.click_count += 1
        self.label.setText(f"Button clicked {self.click_count} time(s).")

        # Create a simple colored pixmap as a visual update.
        colors = ["#e74c3c", "#3498db", "#2ecc71", "#f39c12", "#9b59b6"]
        color = colors[self.click_count % len(colors)]
        pixmap = QPixmap(200, 200)
        pixmap.fill(Qt.GlobalColor.white)

        from PyQt6.QtGui import QPainter, QBrush, QColor
        painter = QPainter(pixmap)
        painter.setBrush(QBrush(QColor(color)))
        painter.drawRect(10, 10, 180, 180)
        painter.end()

        self.image_label.setPixmap(pixmap)

        # Force a repaint to ensure the display updates on macOS.
        self.label.update()
        self.image_label.update()


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

Run this script and click the button. The label text and the colored square should both update immediately each time, without needing to switch windows.

Summary

Fix When to use it
os.environ["QT_MAC_WANTS_LAYER"] = "1" First thing to try on macOS Mojave
.update() on widgets When a specific widget isn't repainting
QApplication.processEvents() When updates are delayed until a function returns
Upgrade PyQt / Qt Newer versions have macOS rendering fixes built in
Upgrade macOS Later macOS versions resolved some rendering bugs

If you're just getting started with Python GUI development, the most painless path forward is to use PyQt6 or PySide6 on a reasonably up-to-date version of macOS. Most of these rendering quirks simply don't appear with that combination.

Bring Your PyQt/PySide Application to Market — Specialized launch support for scientific and engineering software built using Python & Qt.

Find out More

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

Fixing PyQt Redraw Issues on macOS was written by Martin Fitzpatrick.

Martin Fitzpatrick is the creator of Python GUIs, and has been developing Python/Qt applications for the past 12+ years. He has written a number of popular Python books and provides Python software development & consulting for teams and startups.