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.
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
}
}
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