Created
March 2, 2025 04:31
-
-
Save jaywardell/c5806f26e9bdacf730c4629063289e7d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // A Simple AppKit NSViewControllerRepresentable to wrap a NSViewCOntroller subclass that presents a NSImageView in a NSScrollVIew. | |
| // This was done more or less for educational purposes for myself, but maybe someone will find it helpful | |
| public struct ImageRepresentable: NSViewControllerRepresentable { | |
| public typealias NSViewControllerType = ImageViewController | |
| let image: NSImage | |
| let minSize: CGSize? | |
| init(image: NSImage, minSize: CGSize? = nil) { | |
| self.image = image | |
| self.minSize = minSize | |
| } | |
| @Environment(\.dismiss) var dismiss: DismissAction | |
| static var urlIndicator: String { String(describing: Self.self) } | |
| @State private var viewController: ImageViewController? | |
| fileprivate func closeWindow() { | |
| dismiss() | |
| } | |
| public func makeNSViewController(context: Context) -> ImageViewController { | |
| ImageViewController(image) | |
| } | |
| public func updateNSViewController(_ uiViewController: ImageViewController, context: Context) { | |
| } | |
| @available(macOS 14.0, *) | |
| @MainActor @preconcurrency public func sizeThatFits(_ proposal: ProposedViewSize, nsViewController: ImageViewController, context: Self.Context) -> CGSize? { | |
| let viewSize = nsViewController.scroller.contentSize | |
| guard let screenSize = NSScreen.main?.frame.size else { | |
| // this shouldn't really happen, but to be safe.. | |
| return proposal.replacingUnspecifiedDimensions(by: viewSize) | |
| } | |
| let imageSize = nsViewController.scroller.documentView?.bounds.size ?? image.size | |
| var sizes = [ | |
| screenSize, | |
| imageSize, | |
| ] | |
| // if the image is smaller than the screen, then the image size is the smallest it can get | |
| if let minSize, | |
| imageSize.width > screenSize.width || | |
| imageSize.height > screenSize.height { | |
| sizes.append(minSize) | |
| } | |
| let ideal = sizes.reduce(screenSize) { | |
| CGSize(width: min($0.width, $1.width), | |
| height: min($0.height, $1.height)) | |
| } | |
| // don't allow a zero size ever | |
| if proposal == .zero { | |
| return ideal | |
| } | |
| return proposal.replacingUnspecifiedDimensions(by: ideal) | |
| } | |
| } | |
| public class ImageViewController: NSViewController { | |
| let image: NSImage | |
| init(_ image: NSImage) { | |
| self.image = image | |
| super.init(nibName: nil, bundle: nil) | |
| self.representedObject = image | |
| } | |
| required init?(coder: NSCoder) { | |
| fatalError("init(coder:) has not been implemented") | |
| } | |
| private(set) var scroller: NSScrollView! | |
| public override func loadView() { | |
| let imageRect = CGRect(origin: .zero, size: image.size) | |
| let imageView = NSImageView(frame: imageRect) | |
| imageView.bounds = imageRect | |
| imageView.image = image | |
| self.scroller = NSScrollView() | |
| scroller.hasHorizontalScroller = true | |
| scroller.hasVerticalScroller = true | |
| scroller.borderType = .noBorder | |
| scroller.addSubview(imageView) | |
| scroller.documentView = imageView | |
| scroller.allowsMagnification = true | |
| self.view = scroller | |
| view.frame = CGRect(origin: .zero, size: image.size) | |
| } | |
| public override func viewWillAppear() { | |
| super.viewWillAppear() | |
| // NOTE: AppKit uses a Quadrant I coordinate system, so the top of the image is at the highest number | |
| // NOTE: This code has no effect if called in viewDidLoad(). | |
| // It's not until viewWillAppear() that the view is setup in such a way that it works | |
| let top = NSPoint(x: 0, y: image.size.height) | |
| scroller?.documentView?.scroll(top) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment