I want to implement an image preview feature, but I encountered a problem: when the image is in full screen, even though the status bar is hidden, the safe area remains blank, as shown in the picture. img1 How to successfully ignore the safe area?
import SwiftUI
import UIKit
class PreviewViewController : UIViewController {
var isDarkMode: Bool = false {
didSet {
updateBackgroundColor()
}
}
var image: UIImage? {
didSet {
guard let image = image else {
imageView.image = nil
return
}
imageView.image = image
}
}
let imageView: UIImageView = UIImageView(frame: .zero)
var fullscreen = false {
didSet {
guard oldValue != fullscreen else { return }
UIView.animate(withDuration: 0.3) {
self.updateNavigationBar()
self.updateStatusBar()
self.updateBackgroundColor()
}
}
}
let scrollView = UIScrollView(frame: .zero)
let singleTapRecognizer = UITapGestureRecognizer()
let doubleTapRecognizer = UITapGestureRecognizer()
override var prefersStatusBarHidden : Bool {
return fullscreen
}
required init() {
super.init(nibName: nil, bundle: nil)
setupScrollView()
setupImageView()
setupSingleTapRecognizer()
setupDoubleTapRecognizer()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupScrollView() {
scrollView.frame = view.bounds
scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
scrollView.delegate = self
scrollView.minimumZoomScale = 1
scrollView.maximumZoomScale = 3
if #available(iOS 11.0, *) {
scrollView.contentInsetAdjustmentBehavior = .never
}
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
view.addSubview(scrollView)
}
private func setupImageView() {
imageView.frame = scrollView.bounds
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
imageView.contentMode = .scaleAspectFit
scrollView.addSubview(imageView)
}
private func setupSingleTapRecognizer() {
singleTapRecognizer.numberOfTapsRequired = 1
singleTapRecognizer.addTarget(self, action: #selector(didSingleTap(_:)))
singleTapRecognizer.require(toFail: doubleTapRecognizer)
view.addGestureRecognizer(singleTapRecognizer)
}
private func setupDoubleTapRecognizer() {
doubleTapRecognizer.numberOfTapsRequired = 2
doubleTapRecognizer.addTarget(self, action: #selector(didDoubleTap(_:)))
view.addGestureRecognizer(doubleTapRecognizer)
}
override func viewDidLoad() {
super.viewDidLoad()
updateBackgroundColor()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
fullscreen = false
}
private func toggleFullscreen() {
fullscreen = !fullscreen
}
@objc func didSingleTap(_ recognizer: UIGestureRecognizer) {
toggleFullscreen()
}
@objc func didDoubleTap(_ recognizer: UIGestureRecognizer) {
if scrollView.zoomScale > 1 {
scrollView.setZoomScale(1, animated: true)
} else {
scrollView.zoom(to: zoomRect(scale: 2, center: recognizer.location(in: recognizer.view)), animated: true)
}
}
private func zoomRect(scale: CGFloat, center: CGPoint) -> CGRect {
guard let zoomView = viewForZooming(in: scrollView) else { return .zero }
let newCenter = scrollView.convert(center, from: zoomView)
var zoomRect = CGRect.zero
zoomRect.size.height = zoomView.frame.size.height / scale
zoomRect.size.width = zoomView.frame.size.width / scale
zoomRect.origin.x = newCenter.x - (zoomRect.size.width / 2.0)
zoomRect.origin.y = newCenter.y - (zoomRect.size.height / 2.0)
return zoomRect
}
private func updateNavigationBar() {
navigationController?.setNavigationBarHidden(fullscreen, animated: true)
}
private func updateStatusBar() {
self.setNeedsStatusBarAppearanceUpdate()
}
private func updateBackgroundColor() {
let aColor: UIColor
if self.fullscreen {
aColor = UIColor.black
} else {
print("Dark Mode Status: \(isDarkMode)")
aColor = isDarkMode ? UIColor.black : UIColor.white
}
view.backgroundColor = aColor
}
}
extension PreviewViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
if scrollView.zoomScale > 1 {
fullscreen = true
guard let image = imageView.image else { return }
guard let zoomView = viewForZooming(in: scrollView) else { return }
let widthRatio = zoomView.frame.width / image.size.width
let heightRatio = zoomView.frame.height / image.size.height
let ratio = widthRatio < heightRatio ? widthRatio:heightRatio
let newWidth = image.size.width * ratio
let newHeight = image.size.height * ratio
let left = 0.5 * (newWidth * scrollView.zoomScale > zoomView.frame.width ? (newWidth - zoomView.frame.width) : (scrollView.frame.width - scrollView.contentSize.width))
let top = 0.5 * (newHeight * scrollView.zoomScale > zoomView.frame.height ? (newHeight - zoomView.frame.height) : (scrollView.frame.height - scrollView.contentSize.height))
scrollView.contentInset = UIEdgeInsets(top: top.rounded(), left: left.rounded(), bottom: top.rounded(), right: left.rounded())
} else {
scrollView.contentInset = .zero
}
}
}
struct PreviewView: UIViewControllerRepresentable {
var darkMode: Bool
var image: UIImage?
func makeUIViewController(context: Context) -> PreviewViewController {
let viewController = PreviewViewController()
viewController.isDarkMode = darkMode
viewController.image = image
return viewController
}
func updateUIViewController(_ uiViewController: PreviewViewController, context: Context) {
uiViewController.isDarkMode = darkMode
uiViewController.image = image
}
}
struct ImagePreview_Previews: PreviewProvider {
static var previews: some View {
PreviewView(darkMode: false, image: UIImage(named: "ppsd"))
}
}
I tried using AutoLayout, but the blank space still exists.
view.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
I also tried to solve the problem through ChatGPT-4, but without exception.