Swift on iOS 17 - Popover with modalPresentationStyle is not transparent

433 views Asked by At

I have a problem with Popover on iOS17. On iOS15 and iOS16, my code works fine.

Problem

Even if the backgroundColor of the view of the ViewController's view to be displayed in Popover is set to clear, it is not transparent in iOS17.

on iOS17.0

on iOS16.4

My Code

Here is my code.

class MessageRoomView: UIViewController, UIPopoverPresentationControllerDelegate, MessagePopMenuViewControllerDelegate, UIGestureRecognizerDelegate {
    
    private func presentPopover(indexPath: IndexPath, sourceRect: CGRect, type: CustomMessageType, isLoginUser: Bool) {
        guard let currentCell = messageCollectionView.cellForItem(at: indexPath) else {
            initPopoverItem()
            return
        }
        self.popoverItem.indexPath = indexPath
        // current cell position from indexPath
        var cellPosition: CGRect {
            let point = CGPoint(x: currentCell.frame.origin.x - messageCollectionView.contentOffset.x, y: currentCell.frame.origin.y - messageCollectionView.contentOffset.y)
            let size = currentCell.bounds.size
            return CGRect(x: point.x, y: point.y, width: size.width, height: size.height)
        }
        // Determination of whether the user is present in the location/range
        guard let navBarHeight = self.navigationController?.navigationBar.frame.size.height else {
            initPopoverItem()
            return
        }
        let isOutOfRange = cellPosition.height > (messageCollectionView.frame.height - navBarHeight - 300)
        
        /* 1. Check if Message size is within range */
        if !isOutOfRange {
            // Determines whether it is upward or downward
            let isUpper = cellPosition.minY <= messageCollectionView.frame.minY + 210
            
            let storyboard = UIStoryboard(name: MessagePopMenuViewController.storyboardName, bundle: nil)
            let popMenuVC = storyboard.instantiateViewController(identifier: MessagePopMenuViewController.storybaordId) { coder in
                return MessagePopMenuViewController(coder: coder, isLoginUser: isLoginUser, isUpper: isUpper, type: type)
            }
            popMenuVC.delegate = self
            popMenuVC.modalPresentationStyle = .popover
            popMenuVC.popoverPresentationController?.sourceView = currentCell
            popMenuVC.popoverPresentationController?.sourceRect = sourceRect
            popMenuVC.popoverPresentationController?.permittedArrowDirections = sourceRect.width > 50 ? (isUpper ? .up : .down) : .unknown
            popMenuVC.popoverPresentationController?.popoverBackgroundViewClass = MessagePopoverBackgroundView.self
            popMenuVC.popoverPresentationController?.delegate = self
            
            present(popMenuVC, animated: true)
            
        } else {
            
            let minYThreshold = messageCollectionView.frame.minY + 60
            let maxYThreshold = messageCollectionView.frame.maxY - 180
            let minYInThreshold = cellPosition.minY >= minYThreshold && cellPosition.minY <= maxYThreshold
            let maxYInThreshold = cellPosition.maxY >= minYThreshold && cellPosition.maxY <= maxYThreshold
            let isWithinThreshold = minYInThreshold || maxYInThreshold
            
            /* 2.a. Judges whether the PopMenu exists within the range that can be displayed on either the top or bottom of the screen. */
            if isWithinThreshold {
                // Determines whether it is upward or downward
                let isUpper = maxYInThreshold
                
                let storyboard = UIStoryboard(name: MessagePopMenuViewController.storyboardName, bundle: nil)
                let popMenuVC = storyboard.instantiateViewController(identifier: MessagePopMenuViewController.storybaordId) { coder in
                    return MessagePopMenuViewController(coder: coder, isLoginUser: isLoginUser, isUpper: isUpper, type: type)
                }
                popMenuVC.delegate = self
                popMenuVC.modalPresentationStyle = .popover
                popMenuVC.popoverPresentationController?.sourceView = currentCell
                popMenuVC.popoverPresentationController?.sourceRect = sourceRect
                popMenuVC.popoverPresentationController?.permittedArrowDirections = isUpper ? .up : .down
                popMenuVC.popoverPresentationController?.popoverBackgroundViewClass = MessagePopoverBackgroundView.self
                popMenuVC.popoverPresentationController?.delegate = self
                
                present(popMenuVC, animated: true)
                
            } else {
                /* 2.b. Popover on either the top or bottom if it does not exist within the displayable area, or in the center if it does not exist within the displayable area */
                if cellPosition.midY > 0 && cellPosition.midY < messageCollectionView.frame.maxY {
                    
                    let storyboard = UIStoryboard(name: MessagePopMenuViewController.storyboardName, bundle: nil)
                    let popMenuVC = storyboard.instantiateViewController(identifier: MessagePopMenuViewController.storybaordId) { coder in
                        return MessagePopMenuViewController(coder: coder, isLoginUser: isLoginUser, isUpper: false, type: type)
                    }
                    let rect = CGRect(x: sourceRect.minX, y: sourceRect.maxY - sourceRect.height / 2, width: sourceRect.width, height: sourceRect.height / 2)
                    popMenuVC.delegate = self
                    popMenuVC.modalPresentationStyle = .popover
                    popMenuVC.popoverPresentationController?.sourceView = currentCell
                    popMenuVC.popoverPresentationController?.sourceRect = rect
                    popMenuVC.popoverPresentationController?.permittedArrowDirections = .down
                    popMenuVC.popoverPresentationController?.popoverBackgroundViewClass = MessagePopoverBackgroundView.self
                    popMenuVC.popoverPresentationController?.delegate = self
                    
                    present(popMenuVC, animated: true)
                } else {
                    /* 2.c. out of range */
                    initPopoverItem()
                    return
                }
            }
        }
    }
    
    func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
        return .none
    }






import UIKit

final class MessagePopoverBackgroundView: UIPopoverBackgroundView {
    
    override func layoutSubviews() {
        super.layoutSubviews()
        layer.shadowOpacity = 0
        setupPathLayer()
    }
    
    override func didMoveToWindow() {
        super.didMoveToWindow()
        let scenes = UIApplication.shared.connectedScenes
        if let windowScene = scenes.first as? UIWindowScene, let window = windowScene.windows.first {
            let transitionViews = window.subviews.filter { String(describing: type(of: $0)) == "UITransitionView" }
            for transitionView in transitionViews {
                let shadowView = transitionView.subviews.filter { String(describing: type(of: $0)) == "_UICutoutShadowView" }.first
                shadowView?.isHidden = true
            }
        }
    }
    
    // MARK: - UIPopoverBackgroundViewMethods
    
    override static func arrowBase() -> CGFloat {
        return 10
    }
    
    override static func arrowHeight() -> CGFloat {
        return 10
    }
    
    override static func contentViewInsets() -> UIEdgeInsets {
        return .zero
    }
    
    // MARK: - UIPopoverBackgroundView properties
    
    private var _arrowOffset: CGFloat = 0
    override var arrowOffset: CGFloat {
        get { return _arrowOffset }
        set { _arrowOffset = newValue }
    }
    
    private var _arrowDirection: UIPopoverArrowDirection = .unknown
    override var arrowDirection: UIPopoverArrowDirection {
        get { return _arrowDirection }
        set { _arrowDirection = newValue }
    }
    
    
    // MARK: - Drawing
    
    /// UIBezierPathで吹き出しを描画する
    private func setupPathLayer() {
        layer.sublayers?.forEach { $0.removeFromSuperlayer() }
        
        let rect = bounds
        let pathLayer = CAShapeLayer()
        pathLayer.frame = rect
        pathLayer.path = generatePath(rect).cgPath
        pathLayer.fillColor = UIColor.darkGray.cgColor
        pathLayer.strokeColor = UIColor.darkGray.cgColor
        pathLayer.lineWidth = 1.0
        layer.addSublayer(pathLayer)
    }
    
    private func generatePath(_ rect: CGRect) -> UIBezierPath {
        let insets: UIEdgeInsets = {
            var insets = MessagePopoverBackgroundView.contentViewInsets()
            if _arrowDirection == .up {
                insets.top += MessagePopoverBackgroundView.arrowHeight()
            } else if _arrowDirection == .down {
                insets.bottom += MessagePopoverBackgroundView.arrowHeight()
            }
            return insets
        }()
        
        let arrowBase = MessagePopoverBackgroundView.arrowBase()
        let arrowCenterX = rect.size.width / 2 + _arrowOffset
        
        let path = UIBezierPath()
        if _arrowDirection == .up {
            path.move(to: CGPoint(x: arrowCenterX - arrowBase / 2, y: insets.top))
            path.addLine(to: CGPoint(x: arrowCenterX, y: 0))
            path.addLine(to: CGPoint(x: arrowCenterX + arrowBase / 2, y: insets.top))
            path.addLine(to: CGPoint(x: arrowCenterX - arrowBase / 2, y: insets.top))
        } else if _arrowDirection == .down {
            path.move(to: CGPoint(x: arrowCenterX - arrowBase / 2, y: rect.maxY - insets.bottom))
            path.addLine(to: CGPoint(x: arrowCenterX, y: rect.maxY))
            path.addLine(to: CGPoint(x: arrowCenterX + arrowBase / 2, y: rect.maxY - insets.bottom))
            path.addLine(to: CGPoint(x: arrowCenterX - arrowBase / 2, y: rect.maxY - insets.bottom))
        }
        path.close()
        
        return path
    }
}

enter image description here

import UIKit

final class MessagePopMenuViewController: UIViewController {
    
    static let storyboardName = "MessagePopMenuViewController"
    static let storybaordId = "MessagePopMenuViewController"
    
    @IBOutlet weak var replyButton: UIButton!
    @IBOutlet weak var copyButton: UIButton!
    @IBOutlet weak var stickerButton: UIButton!
    @IBOutlet weak var showImageButton: UIButton!
    @IBOutlet weak var unsendButton: UIButton!
    @IBOutlet weak var menuSpaceView: UIView!
    @IBOutlet weak var topReactionStackView: UIStackView!
    @IBOutlet weak var bottomReactionStackView: UIStackView!
    @IBOutlet var reactionButtons: [UIButton]!
    
    private let isLoginUser: Bool
    private let isUpper: Bool
    private let type: CustomMessageType
    
    weak var delegate: MessagePopMenuViewControllerDelegate?
    private let reactionArray = ["❤️", "", "", "", ""]
    
    init?(coder: NSCoder, isLoginUser: Bool, isUpper: Bool, type: CustomMessageType) {
        self.isLoginUser = isLoginUser
        self.isUpper = isUpper
        self.type = type
        super.init(coder: coder)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        // print("MessagePopMenuViewController deinit")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        reactionButtons.forEach({ $0.backgroundColor = .clear })
        topReactionStackView.backgroundColor = .clear
        bottomReactionStackView.backgroundColor = .clear
        
        switch (isLoginUser, isUpper) {
        case (true, true) :
            topReactionStackView.isHidden = true
            menuSpaceView.isHidden = true
            self.preferredContentSize = CGSize(width: 261, height: 130)
            replyButton.clipsToBounds = true
            replyButton.layer.cornerRadius = 8.0
            replyButton.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
            unsendButton.clipsToBounds = true
            unsendButton.layer.cornerRadius = 8.0
            unsendButton.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
            
        case (true, false):
            bottomReactionStackView.isHidden = true
            menuSpaceView.isHidden = true
            self.preferredContentSize = CGSize(width: 261, height: 130)
            replyButton.clipsToBounds = true
            replyButton.layer.cornerRadius = 8.0
            replyButton.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
            unsendButton.clipsToBounds = true
            unsendButton.layer.cornerRadius = 8.0
            unsendButton.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
            
        case (false, true):
            topReactionStackView.isHidden = true
            unsendButton.isHidden = true
            self.preferredContentSize = CGSize(width: 251, height: 130)
            replyButton.clipsToBounds = true
            replyButton.layer.cornerRadius = 8.0
            replyButton.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
            menuSpaceView.clipsToBounds = true
            menuSpaceView.layer.cornerRadius = 8.0
            menuSpaceView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
            
        case (false, false):
            bottomReactionStackView.isHidden = true
            unsendButton.isHidden = true
            self.preferredContentSize = CGSize(width: 251, height: 130)
            replyButton.clipsToBounds = true
            replyButton.layer.cornerRadius = 8.0
            replyButton.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
            menuSpaceView.clipsToBounds = true
            menuSpaceView.layer.cornerRadius = 8.0
            menuSpaceView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
        }
        
        switch type {
        case .text, .reply:
            stickerButton.isHidden = true
            showImageButton.isHidden = true
        case .image:
            copyButton.isHidden = true
            stickerButton.isHidden = true
        case .sticker:
            copyButton.isHidden = true
            showImageButton.isHidden = true
        case.talk:
            self.dismiss(animated: false)
        }
    }
    
    // some IBAction later
}

Xcode - Version 15.0 (15A240d) iOS - 17.0 macOS - macOS Sonoma version14.0

0

There are 0 answers