Skip to content

[Task, UI/UX] Transit to native macOS-aligned UI patterns and system styling when running on macOS #932

Description

@adamantmm

Summary

Transition ADAMANT Messenger (adamant-iOS) to native macOS-aligned UI patterns and system styling when running on macOS (Mac Catalyst), removing custom visual workarounds and aligning with current Apple Human Interface Guidelines. This includes replacing manual styling (custom backgrounds, selection layers, hardcoded colors/insets) with semantic system colors, native list/split configurations, and modern configuration APIs to ensure correct behavior in Light/Dark modes, accent color variations, and future macOS updates.

Motivation

With recent macOS updates, parts of the UI (split layout, selection states, grouped blocks, toolbars, insets) no longer render correctly or consistently. Some selection backgrounds, block highlighting, and layout spacing appear visually broken or non-native.

The root cause is not a single bug but accumulated custom styling and layout adjustments that diverge from system-driven behavior.

Goals:

  • Eliminate fragile, OS-sensitive UI workarounds
  • Adopt native split view and sidebar patterns
  • Ensure correct rendering in Light/Dark mode and with different system accent colors
  • Improve forward compatibility with future macOS releases
  • Reduce maintenance burden caused by custom visual logic

Detailed description

1. Sidebar and Selection Behavior

Current issues:

  • Custom selection backgrounds conflict with system highlight behavior
  • Selected/active states may not match macOS sidebar expectations
  • Focus state may not be visually distinguishable

Actions:

  • Remove manual selected/highlighted background drawing
  • Migrate list cells to modern configuration APIs (UIListContentConfiguration, UIBackgroundConfiguration)
  • Use system sidebar/list configurations instead of custom background layers
  • Ensure selection updates via updateConfiguration(using:) based on UICellConfigurationState
  • Avoid hardcoded selection colors

Expected result:

Native macOS sidebar behavior with correct highlight, focus, and accent color adaptation.

2. Split View Architecture

Current issues:

  • Inconsistent layout between primary and secondary columns
  • Incorrect resizing behavior when window is resized
  • Manual width handling instead of system-driven behavior

Actions:

  • Ensure UISplitViewController is used in .doubleColumn style
  • Configure preferredDisplayMode appropriately for macOS behavior
  • Remove manual layout adjustments that simulate master-detail behavior
  • Validate primary column width configuration and resizing constraints

Expected result:
Stable, native two-pane layout consistent with macOS conventions.

3. System Colors and Materials

Current issues:

  • Hardcoded RGB values for backgrounds and separators
  • Custom alpha/blur combinations that break in Dark Mode or high contrast
  • Inconsistent appearance under different system accent colors

Actions:

  • Replace custom colors with semantic system colors (label, secondaryLabel, systemBackground, secondarySystemBackground, separator, tertiarySystemFill, etc.)
  • Avoid fixed opacity values unless required
  • Test with:
    • Light and Dark modes
    • Different system accent colors
    • Increase Contrast
    • Reduce Transparency

Expected result:
Visual consistency across themes and accessibility settings.

4. Toolbar, Navigation Bar, and Insets

Current issues:

  • Hardcoded top insets
  • Layout assumptions tied to iOS navigation bar behavior
  • Content overlapping or misaligned under macOS title/toolbar

Actions:

  • Remove fixed height calculations for top bars
  • Avoid manual status bar offsets
  • Review UINavigationBarAppearance and UIToolbarAppearance usage
  • Let safe areas and system layout guides drive positioning

Expected result:
Correct alignment with macOS unified title/toolbar styling.

5. Cards, Grouped Blocks, and Rounded Containers

Current issues:

  • Custom “card” visuals conflicting with system grouping
  • Mismatched corner radius vs selection highlight
  • Layout artifacts during window resizing

Actions:

  • Prefer system background grouping instead of layered CALayer styling
  • Minimize custom corner radius unless essential
  • Avoid stacking background layers that override system selection behavior

Expected result:
Cleaner layout with fewer visual artifacts during resizing and state changes.

6. macOS-Specific Interaction Patterns

Current issues:

  • Long-press logic reused from iOS for context actions
  • Hover and right-click behavior inconsistent with macOS expectations
  • Limited keyboard shortcut support

Actions:

  • Ensure context menus use UIContextMenuInteraction / UIMenu
  • Validate right-click behavior on macOS
  • Review focus handling
  • Improve keyboard shortcut support where applicable

Expected result:
Proper macOS interaction model without iOS-centric behavior leaks.

Notes

  • This is not a purely visual refactor; it improves architectural alignment with Apple platform guidelines
  • Changes should be incremental and validated screen by screen (Chats, Wallet, Settings)
  • Avoid introducing new custom visual abstractions during migration
  • Performance during window resizing must be monitored to prevent layout thrashing
  • Testing must include real macOS hardware, not only simulator

Screenshots or videos

Image Image Image Image Image Image

Alternatives

  1. Maintain current custom styling and patch individual breakages per macOS release.
    This approach increases long-term maintenance cost and risk of regression.
  2. Fork macOS-specific UI layer.
    Higher complexity and code duplication, not preferred unless architectural divergence becomes necessary.

Preferred approach: adopt system-native styling and configurations within the existing Catalyst architecture.

Proposed technical implementation

Some useful (or not) code parts:

// 1) Native sidebar-style cell using modern configurations (UIKit / Catalyst)
//
// Use this for the Chats list / Accounts list on macOS.
// Key idea: don't draw selection backgrounds manually.
// Let UIBackgroundConfiguration + UIListContentConfiguration handle it.

final class SidebarCell: UITableViewCell {
    private var titleText: String = ""
    private var subtitleText: String?

    func configure(title: String, subtitle: String? = nil) {
        titleText = title
        subtitleText = subtitle
        setNeedsUpdateConfiguration()
    }

    override func updateConfiguration(using state: UICellConfigurationState) {
        // Content
        var content = UIListContentConfiguration.sidebarCell().updated(for: state)
        content.text = titleText
        content.secondaryText = subtitleText
        content.textProperties.color = .label
        content.secondaryTextProperties.color = .secondaryLabel
        contentConfiguration = content

        // Background
        var bg = UIBackgroundConfiguration.listSidebarCell().updated(for: state)
        backgroundConfiguration = bg

        // Avoid interfering with system selection appearance.
        selectedBackgroundView = nil
        multipleSelectionBackgroundView = nil
    }
}
// 2) Applying native list appearance to a UITableView (iOS + macOS Catalyst)
//
// This removes the need for custom separators and manual grouped backgrounds.
// It's particularly helpful for Settings/Wallet-style grouped screens.

func applyNativeListAppearance(_ tableView: UITableView) {
    tableView.backgroundColor = .systemBackground
    tableView.separatorColor = .separator
    tableView.separatorInset = .zero
    tableView.cellLayoutMarginsFollowReadableWidth = true

    // Pick one depending on your screen. For Settings-like views:
    tableView.separatorStyle = .singleLine
    tableView.estimatedRowHeight = 52
    tableView.rowHeight = UITableView.automaticDimension

    // If you previously used a custom table footer to hide empty separators:
    tableView.tableFooterView = UIView()
}
// 3) A "do not hardcode colors" helper: semantic palette for cards/blocks
//
// Use these instead of RGB constants when drawing "cards" or section backgrounds.
// It will adapt to Light/Dark, accessibility contrast, and macOS accent settings.

enum SystemPalette {
    static let pageBackground: UIColor = .systemBackground
    static let groupBackground: UIColor = .secondarySystemBackground
    static let elevatedBackground: UIColor = .tertiarySystemBackground

    static let primaryText: UIColor = .label
    static let secondaryText: UIColor = .secondaryLabel

    static let separator: UIColor = .separator
    static let fill: UIColor = .tertiarySystemFill
}
// 4) Replace "manual highlight layers" with background configuration for a card-like view
//
// This is useful for "blocks" in Wallet/Settings, where selection/highlight looked broken.
// It uses UIBackgroundConfiguration so the system can manage states consistently.

final class CardLikeCell: UITableViewCell {
    private var titleText: String = ""

    func configure(title: String) {
        titleText = title
        setNeedsUpdateConfiguration()
    }

    override func updateConfiguration(using state: UICellConfigurationState) {
        var content = UIListContentConfiguration.valueCell().updated(for: state)
        content.text = titleText
        content.textProperties.color = .label
        contentConfiguration = content

        var bg = UIBackgroundConfiguration.listGroupedCell().updated(for: state)
        bg.backgroundColor = .secondarySystemBackground
        bg.cornerRadius = 12
        bg.strokeColor = .separator
        bg.strokeWidth = 1 / UIScreen.main.scale
        backgroundConfiguration = bg

        selectedBackgroundView = nil
        multipleSelectionBackgroundView = nil
    }
}
// 5) Proper double-column split view controller configuration (UIKit / Catalyst)
//
// This makes the layout behave like a native macOS two-pane UI.
// Put this where you build your root controller for macOS/Catalyst mode.

func makeRootSplitViewController(primary: UIViewController,
                                 secondary: UIViewController) -> UISplitViewController {
    let svc = UISplitViewController(style: .doubleColumn)
    svc.setViewController(primary, for: .primary)
    svc.setViewController(secondary, for: .secondary)

    // Typical macOS behavior: show both columns side-by-side when possible.
    svc.preferredDisplayMode = .oneBesideSecondary

    // Tune width for sidebar-like primary column.
    svc.preferredPrimaryColumnWidthFraction = 0.32
    svc.minimumPrimaryColumnWidth = 260
    svc.maximumPrimaryColumnWidth = 420

    return svc
}
// 6) Remove fragile top inset hacks and rely on safe area
//
// If you have code that adds manual top padding for "navigation bar height",
// replace it with safeAreaInsets-based layout.

final class SafeAreaPinnedViewController: UIViewController {
    private let contentView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground

        contentView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(contentView)

        NSLayoutConstraint.activate([
            contentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
    }
}
// 7) Context menu that behaves correctly on macOS (right-click) and iOS (long press)
//
// Prefer UIContextMenuInteraction/UIMenu over custom "long press" logic.
// Attach this to message bubbles, chat rows, wallet rows, etc.

final class ContextMenuHost: NSObject, UIContextMenuInteractionDelegate {
    func attach(to view: UIView) {
        let interaction = UIContextMenuInteraction(delegate: self)
        view.addInteraction(interaction)
    }

    func contextMenuInteraction(_ interaction: UIContextMenuInteraction,
                                configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
        UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
            let copy = UIAction(title: "Copy", image: UIImage(systemName: "doc.on.doc")) { _ in
                UIPasteboard.general.string = "..."
            }
            let share = UIAction(title: "Share…", image: UIImage(systemName: "square.and.arrow.up")) { _ in
                // Present share sheet from your controller
            }
            return UIMenu(title: "", children: [copy, share])
        }
    }
}
// 8) Quick runtime switch: Catalyst-only tweaks (when absolutely needed)
//
// Use sparingly. The main goal is to NOT branch styling heavily,
// but sometimes you need small adjustments for macOS behavior.

extension ProcessInfo {
    static var isMacCatalystApp: Bool {
        #if targetEnvironment(macCatalyst)
        return true
        #else
        return false
        #endif
    }
}

func applyCatalystTweaksIfNeeded() {
    guard ProcessInfo.isMacCatalystApp else { return }

    // Example: prefer larger default content sizes for desktop readability
    UILabel.appearance().adjustsFontForContentSizeCategory = true
}

Verification Checklist

Visual:

  • Chats screen renders correctly in Light and Dark mode
  • Sidebar selection uses native highlight and respects system accent color
  • Wallet and Settings layouts maintain correct split behavior
  • No overlapping content in title/toolbar area
  • Grouped blocks look native and consistent

Interaction:

  • Selection state remains correct when switching windows
  • Right-click and context menus behave properly on macOS
  • Window resizing does not cause layout glitches
  • No flickering or redraw artifacts in lists

Accessibility:

  • UI remains readable with Increase Contrast enabled
  • UI behaves correctly with Reduce Transparency enabled
  • No hardcoded color assumptions break visibility

Regression:

  • iOS appearance remains unaffected
  • No functional regressions in chat, wallet, or settings modules

Outcome:
ADAMANT Messenger on macOS behaves as a first-class native Mac app rather than an iOS app with visual overrides.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Composite taskLarge or complex task that consists of multiple subtaskDesktopIssues specific to desktop clientsSwiftiOS-specific code and features in SwiftUX/UIUser interface and experience improvementsenhancementNew feature or request

    Type

    Fields

    No fields configured for Task.

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions