Skip to content

Commit 025e8a2

Browse files
authored
Fix NSScroller layout with Auto Layout constraints (#469)
Problem When TerminalView is embedded using Auto Layout constraints (e.g., via SwiftUI's NSViewRepresentable with translatesAutoresizingMaskIntoConstraints = false), the NSScroller appears mispositioned - stuck in the bottom-right corner, scrolling horizontally instead of vertically. Root Cause The scroller was positioned using autoresizingMask (frame-based layout), which conflicts with constraint-based layout. The frame calculations in getScrollerFrame() relied on bounds.maxX which may not be accurate before the layout pass completes. Solution Use Auto Layout constraints to position the scroller: Scroller is pinned to trailing/top/bottom anchors of the parent view Width uses the system-provided NSScroller.scrollerWidth() value Works correctly whether parent uses frame-based or constraint-based layout Changes setupScroller() now uses Auto Layout constraints instead of autoresizing masks updateScrollerFrame() is now a no-op (constraints handle positioning) Added scrollerWidth computed property for reliable width calculation getScrollerFrame() preserved for backwards compatibility
1 parent 08086f9 commit 025e8a2

File tree

1 file changed

+21
-10
lines changed

1 file changed

+21
-10
lines changed

Sources/SwiftTerm/Mac/MacTerminalView.swift

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -386,24 +386,31 @@ open class TerminalView: NSView, NSTextInputClient, NSUserInterfaceValidations,
386386
}
387387

388388
let scrollerStyle: NSScroller.Style = .legacy
389+
389390
func getScrollerFrame() -> CGRect {
390391
let scrollerWidth = NSScroller.scrollerWidth(for: .regular, scrollerStyle: scrollerStyle)
391392
return NSRect(x: bounds.maxX - scrollerWidth, y: 0, width: scrollerWidth, height: bounds.height)
392393
}
393394

394395
func setupScroller()
395396
{
396-
let scrollerFrame = getScrollerFrame()
397397
if scroller == nil {
398-
scroller = NSScroller(frame: scrollerFrame)
399-
} else {
400-
scroller?.frame = scrollerFrame
398+
scroller = NSScroller(frame: .zero)
399+
scroller.translatesAutoresizingMaskIntoConstraints = false
400+
addSubview(scroller)
401+
402+
// Use Auto Layout to position the scroller. This ensures correct layout
403+
// whether the parent view uses frame-based or constraint-based layout.
404+
NSLayoutConstraint.activate([
405+
scroller.trailingAnchor.constraint(equalTo: trailingAnchor),
406+
scroller.topAnchor.constraint(equalTo: topAnchor),
407+
scroller.bottomAnchor.constraint(equalTo: bottomAnchor),
408+
scroller.widthAnchor.constraint(equalToConstant: scrollerWidth)
409+
])
401410
}
402-
scroller.autoresizingMask = [.minXMargin, .height]
403411
scroller.scrollerStyle = scrollerStyle
404412
scroller.knobProportion = 0.1
405413
scroller.isEnabled = false
406-
addSubview (scroller)
407414
if let progressBarView {
408415
addSubview(progressBarView, positioned: .above, relativeTo: scroller)
409416
}
@@ -412,7 +419,7 @@ open class TerminalView: NSView, NSTextInputClient, NSUserInterfaceValidations,
412419
}
413420

414421
func updateScrollerFrame() {
415-
scroller?.frame = getScrollerFrame()
422+
// Scroller position is managed by Auto Layout constraints
416423
}
417424

418425
/// This method sents the `nativeForegroundColor` and `nativeBackgroundColor`
@@ -431,17 +438,21 @@ open class TerminalView: NSView, NSTextInputClient, NSUserInterfaceValidations,
431438
terminalDelegate?.send (source: self, data: data)
432439
}
433440

441+
private var scrollerWidth: CGFloat {
442+
NSScroller.scrollerWidth(for: .regular, scrollerStyle: scrollerStyle)
443+
}
444+
434445
/**
435446
* Given the current set of columns and rows returns a frame that would host this control.
436447
*/
437448
open func getOptimalFrameSize () -> NSRect
438449
{
439-
return NSRect (x: 0, y: 0, width: cellDimension.width * CGFloat(terminal.cols) + scroller.frame.width, height: cellDimension.height * CGFloat(terminal.rows))
450+
return NSRect (x: 0, y: 0, width: cellDimension.width * CGFloat(terminal.cols) + scrollerWidth, height: cellDimension.height * CGFloat(terminal.rows))
440451
}
441-
452+
442453
func getEffectiveWidth (size: CGSize) -> CGFloat
443454
{
444-
return (size.width-scroller.frame.width)
455+
return (size.width - scrollerWidth)
445456
}
446457

447458
open func scrolled(source terminal: Terminal, yDisp: Int) {

0 commit comments

Comments
 (0)