How hide blur for UIPopoverPresentationController in swift? - ios

I've implemented popover for UIButton:
#objc func showList(sender: AnyObject?) {
guard let buttonView = sender?.value(forKey: "view") as? UIButton else { return }
guard let popVC = R.storyboard.first.VC() else { return }
popVC.modalPresentationStyle = .popover
let popOverVC = popVC.popoverPresentationController
popOverVC?.delegate = self
popOverVC?.sourceView = buttonView
popOverVC?.sourceRect = CGRect(x: 0, y: 190, width: 0, height: 0)
popOverVC?.permittedArrowDirections = .init(rawValue: 0)
popVC.preferredContentSize = CGSize(width: 150, height: 250)
showedViewController.present(popVC, animated: true, completion: nil)
}
extension VCInteractor: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
It's working good, but I need one fix - I want to hide UIVisualEffectBackdropView for my popover (it's like blur under my UITableViewController). So, I can't find any info how to disable (hide) this blur, do you have any ideas?

I've fixed my issue with my custom class
class PopoverBackgroundView: UIPopoverBackgroundView {
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.shadowColor = UIColor.clear.cgColor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override static func contentViewInsets() -> UIEdgeInsets {
return UIEdgeInsets.zero
}
override static func arrowHeight() -> CGFloat {
return 0
}
override var arrowDirection: UIPopoverArrowDirection {
get { return UIPopoverArrowDirection.down }
set {
setNeedsLayout()
}
}
override var arrowOffset: CGFloat {
get { return 0 }
set {
setNeedsLayout()
}
}
}
and I've added it like that
popOverVC?.popoverBackgroundViewClass = PopoverBackgroundView.self
that's all

here i have a very simple solution.
let appearance = UIVisualEffectView.appearance(whenContainedInInstancesOf: [NSClassFromString("_UIPopoverStandardChromeView")! as! UIAppearanceContainer.Type])
appearance.effect = UIVisualEffect()
add the code to the AppDelegate.swift file or anywhere the app is getting initialized.

I suggest adding this to the PopoverBackgroundView that Vadim Nikolaev has posted above. This removes the "clear halo" effect when removing the blur.
`override func didMoveToWindow() {
super.didMoveToWindow()
if #available(iOS 13, *) {
// iOS 13 (or newer)
if let window = UIApplication.shared.keyWindow {
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
}
}
}
}`

Related

How to customize UIPageControl?

I am a beginner in swift
I want to customize my UIPageControl on only code
The UIPageControl style I want is:I can change the little dot above and when I switch to it he can change to another style
I found an article that provides a how-to, which can be done using
It's good, but I don't understand how it works
The method is attached for reference by friends who have the same needs
but that's not my real intention
I want to understand how this program works, can someone explain it to me? or provide a more simple and understandable method
I know so much code is because it supports versions below ios14
but I think this part may be omitted in explaining
The reason why it is not removed is to seek better and more complete code.
thank everybody
import UIKit
class CZPageControl: UIPageControl {
var currentImage: UIImage?
var inactiveImage: UIImage?
var currentTintColor: UIColor?
var inactiveTintColor: UIColor?
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = false
if #available(iOS 14.0, *) {
self.allowsContinuousInteraction = false
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var currentPage: Int {
didSet {
updateDots()
}
}
func updateDots() {
guard let currentImage = self.currentImage, let inactiveImage = self.inactiveImage else {
return
}
if #available(iOS 14.0, *) {
guard let dotContentView = findIndicatorContentView() else {
return
}
for (index, view) in dotContentView.subviews.enumerated() {
if view.isKind(of: UIImageView.self) {
self.currentPageIndicatorTintColor = self.currentTintColor
self.pageIndicatorTintColor = self.inactiveTintColor
let indicatorView = view as! UIImageView
indicatorView.image = nil
if index == self.currentPage {
indicatorView.image = currentImage.withRenderingMode(.alwaysTemplate)
} else {
indicatorView.image = inactiveImage.withRenderingMode(.alwaysTemplate)
}
}
}
} else {
for (index, view) in self.subviews.enumerated() {
if let dot = imageViewForSubview(view, currentPage: index) {
var size = CGSize.zero
if index == self.currentPage {
dot.tintColor = self.currentTintColor
dot.image = currentImage.withRenderingMode(.alwaysTemplate)
size = dot.image!.size
} else {
dot.tintColor = self.inactiveTintColor
dot.image = inactiveImage.withRenderingMode(.alwaysTemplate)
size = dot.image!.size
}
if let superview = dot.superview {
let x = (superview.frame.size.width - size.width) / 2.0
let y = (superview.frame.size.height - size.height) / 2.0
dot.frame = CGRect(x: x, y: y, width: size.width, height: size.height)
}
}
}
}
}
// iOS 14之前创建UIImageView使用
func imageViewForSubview(_ view: UIView, currentPage: Int) -> UIImageView? {
var dot: UIImageView?
if view.isKind(of: UIView.self) {
for subview in view.subviews {
if subview.isKind(of: UIImageView.self) {
dot = (subview as! UIImageView)
break
}
}
if dot == nil {
dot = UIImageView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
view.addSubview(dot!)
}
} else {
dot = (view as! UIImageView)
}
return dot
}
#available(iOS 14.0, *)
func findIndicatorContentView() -> UIView? {
for contentView in self.subviews {
if let contentViewClass = NSClassFromString("_UIPageControlContentView"), contentView.isKind(of: contentViewClass) {
for indicatorContentView in contentView.subviews {
if let indicatorContentViewClass = NSClassFromString("_UIPageControlIndicatorContentView"), indicatorContentView.isKind(of: indicatorContentViewClass) {
return indicatorContentView
}
}
}
}
return nil
}
}
You will have to call "updateDots()" in viewDidAppear() and your valueChanged handler for the page control.
import UIKit
class CustomImagePageControl: UIPageControl {
let activeImage:UIImage = UIImage(named: "SelectedPage")!
let inactiveImage:UIImage = UIImage(named: "UnselectedPage")!
override func awakeFromNib() {
super.awakeFromNib()
self.pageIndicatorTintColor = UIColor.clear
self.currentPageIndicatorTintColor = UIColor.clear
self.clipsToBounds = false
}
func updateDots() {
var i = 0
for view in self.subviews {
if let imageView = self.imageForSubview(view) {
if i == self.currentPage {
imageView.image = self.activeImage
} else {
imageView.image = self.inactiveImage
}
i = i + 1
} else {
var dotImage = self.inactiveImage
if i == self.currentPage {
dotImage = self.activeImage
}
view.clipsToBounds = false
view.addSubview(UIImageView(image:dotImage))
i = i + 1
}
}
}
fileprivate func imageForSubview(_ view:UIView) -> UIImageView? {
var dot:UIImageView?
if let dotImageView = view as? UIImageView {
dot = dotImageView
} else {
for foundView in view.subviews {
if let imageView = foundView as? UIImageView {
dot = imageView
break
}
}
}
return dot
}
}

use class in swiftui

I have these two classes which are for having the custom tapbar, I would like to use them in swiftUI how can I do? I used these wrappers but once implemented the class in the ContentView does not appear to me
I would like to do everything in swiftUI so I would prefer not to use storyboards in the implementation. it's possible ?
//SwiftUI class. I want to use this view already done in SwiftUI
HStack {
SHCircleBarControllerView()
SHCircleBarView()
}
//Class Swift 4
import UIKit
import SwiftUI
struct SHCircleBarControllerView : UIViewControllerRepresentable {
typealias UIViewControllerType = SHCircleBarController
func makeCoordinator() -> SHCircleBarControllerView.Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<SHCircleBarControllerView>) -> SHCircleBarController {
return SHCircleBarController()
}
func updateUIViewController(_ uiViewController: SHCircleBarController, context: UIViewControllerRepresentableContext<SHCircleBarControllerView>) {
}
class Coordinator : NSObject {
var parent : SHCircleBarControllerView
init(_ viewController : SHCircleBarControllerView){
self.parent = viewController
}
}
}
class SHCircleBarController: UITabBarController {
fileprivate var shouldSelectOnTabBar = true
private var circleView : UIView!
private var circleImageView: UIImageView!
open override var selectedViewController: UIViewController? {
willSet {
guard shouldSelectOnTabBar, let newValue = newValue else {
shouldSelectOnTabBar = true
return
}
guard let tabBar = tabBar as? SHCircleBar, let index = viewControllers?.firstIndex(of: newValue) else {return}
tabBar.select(itemAt: index, animated: true)
}
}
open override var selectedIndex: Int {
willSet {
guard shouldSelectOnTabBar else {
shouldSelectOnTabBar = true
return
}
guard let tabBar = tabBar as? SHCircleBar else {
return
}
tabBar.select(itemAt: selectedIndex, animated: true)
}
}
open override func viewDidLoad() {
super.viewDidLoad()
let tabBar = SHCircleBar()
self.setValue(tabBar, forKey: "tabBar")
self.circleView = UIView(frame: .zero)
circleView.layer.cornerRadius = 30
circleView.backgroundColor = .white
circleView.isUserInteractionEnabled = false
self.circleImageView = UIImageView(frame: .zero)
circleImageView.layer.cornerRadius = 30
circleImageView.isUserInteractionEnabled = false
circleImageView.contentMode = .center
circleView.addSubview(circleImageView)
self.view.addSubview(circleView)
let tabWidth = self.view.bounds.width / CGFloat(self.tabBar.items?.count ?? 4)
circleView.frame = CGRect(x: tabWidth / 2 - 30, y: self.tabBar.frame.origin.y - 40, width: 60, height: 60)
circleImageView.frame = self.circleView.bounds
}
open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
circleImageView.image = image(with: self.tabBar.selectedItem?.image ?? self.tabBar.items?.first?.image, scaledTo: CGSize(width: 30, height: 30))
}
private var _barHeight: CGFloat = 74
open var barHeight: CGFloat {
get {
if #available(iOS 11.0, *) {
return _barHeight + view.safeAreaInsets.bottom
} else {
return _barHeight
}
}
set {
_barHeight = newValue
updateTabBarFrame()
}
}
private func updateTabBarFrame() {
var tabFrame = self.tabBar.frame
tabFrame.size.height = barHeight
tabFrame.origin.y = self.view.frame.size.height - barHeight
self.tabBar.frame = tabFrame
tabBar.setNeedsLayout()
}
open override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
updateTabBarFrame()
}
open override func viewSafeAreaInsetsDidChange() {
if #available(iOS 11.0, *) {
super.viewSafeAreaInsetsDidChange()
}
updateTabBarFrame()
}
open override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
guard let idx = tabBar.items?.firstIndex(of: item) else { return }
if idx != selectedIndex, let controller = viewControllers?[idx] {
shouldSelectOnTabBar = false
selectedIndex = idx
let tabWidth = self.view.bounds.width / CGFloat(self.tabBar.items!.count)
UIView.animate(withDuration: 0.3) {
self.circleView.frame = CGRect(x: (tabWidth * CGFloat(idx) + tabWidth / 2 - 30), y: self.tabBar.frame.origin.y - 15, width: 60, height: 60)
}
UIView.animate(withDuration: 0.15, animations: {
self.circleImageView.alpha = 0
}) { (_) in
self.circleImageView.image = self.image(with: item.image, scaledTo: CGSize(width: 30, height: 30))
UIView.animate(withDuration: 0.15, animations: {
self.circleImageView.alpha = 1
})
}
delegate?.tabBarController?(self, didSelect: controller)
}
}
private func image(with image: UIImage?, scaledTo newSize: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(newSize, _: false, _: 0.0)
image?.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
let newImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
You can set up your custom UIViewControllers inside the SHCircleBarController through the viewControllers property.
In SHCircleBarController
open override func viewDidLoad() {
super.viewDidLoad()
...
viewControllers = [ViewController(), ViewController2()]
}
Your other UIViewControllers
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
}
}
class ViewController2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
}
}
This is the result

UISlider subclass does not move beyond 0 - 1 range

I'm subclassing UISlider and overriding maximumValue to be 5, minimumValue to be 0, isContinuous to be false, and both track tint colors. Is there something I NEED to override to fix this issue, such as draw? I tried setting the slider directly to a value above 1, but it still doesn't go beyond it. Any help would be greatly appreciated.
There's no exceptions, debugging it shows the value that is sent to setValue is changing accordingly, the only issue I'm seeing is that the slider isn't updating visually.
class Slider: UISlider {
private var viewData: ViewData
// MARK: - Initialization
init(viewData: ViewData) {
self.viewData = viewData
super.init(frame: .zero)
render(for: viewData)
translatesAutoresizingMaskIntoConstraints = false
}
override public init(frame: CGRect) {
self.viewData = ViewData(orientation: .vertical, type: .discrete)
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func render(for viewData: ViewData) {
self.addTarget(self, action: #selector(sliderValueDidChange), for: .valueChanged)
}
#objc private func sliderValueDidChange(_ sender: UISlider) {
self.setValue(sender.value, animated: true)
Haptic.triggerImpact(.light)
}
// MARK: - Overrides
override var maximumValue: Float {
get {
switch viewData.type {
case .continuous: return 100
case .discrete: return 5
}
}
set { maximumValue = newValue }
}
override var minimumValue: Float {
get { return 0 }
set { minimumValue = newValue }
}
override var isContinuous: Bool {
get {
switch viewData.type {
case .continuous: return true
case .discrete: return false
}
}
set { isContinuous = newValue }
}
override var minimumTrackTintColor: UIColor? {
get { return .white }
set { minimumTrackTintColor = newValue }
}
override var maximumTrackTintColor: UIColor? {
get { return .gray }
set { minimumTrackTintColor = newValue }
}
override open func trackRect(forBounds bounds: CGRect) -> CGRect {
var newBounds = super.trackRect(forBounds: bounds)
newBounds.size = CGSize(width: 222, height: 10)
return newBounds
}
override open func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect {
var newThumb = super.thumbRect(forBounds: bounds, trackRect: rect, value: value)
newThumb.size = CGSize(width: 30, height: 30)
return newThumb
}
override open func setValue(_ value: Float, animated: Bool) {
guard viewData.type == .discrete else {
super.setValue(value, animated: animated)
return
}
let roundedValue = Int(value)
super.setValue(Float(roundedValue), animated: animated)
setNeedsDisplay()
Haptic.triggerImpact(.light)
}
All of your overridden properties are causing infinite recursion in the set blocks.
Instead of overriding them just to set a specific value, update your render method to set the desired initial values.
private func render(for viewData: ViewData) {
self.addTarget(self, action: #selector(sliderValueDidChange), for: .valueChanged)
minimumValue = 0
switch viewData.type {
case .continuous: maximumValue = 100
case .discrete: maximumValue = 5
}
isContinuous = viewData.type == .continuous
minimumTrackTintColor = .white
maximumTrackTintColor = .gray
}
Besides this update, remove those overridden properties and the slider will work just fine.

Forwarding events with hitTest() or point(inside, with event) from view added to keyWindow

I've got a small, reusable UIView widget that can be added to any view anywhere, and may or may not always be in the same place or have the same frame. It looks something like this:
class WidgetView: UIView {
// some stuff, not very exciting
}
In my widget view, there's a situation where I need to create a popup menu with an overlay underneath it. it looks like this:
class WidgetView: UIView {
// some stuff, not very exciting
var overlay: UIView!
commonInit() {
guard let keyWindow = UIApplication.shared.keyWindow else { return }
overlay = UIView(frame: keyWindow.frame)
overlay.alpha = 0
keyWindow.addSubview(overlay)
// Set some constraints here
someControls = CustomControlsView( ... a smaller controls view ... )
overlay.addSubview(someControls)
// Set some more constraints here!
}
showOverlay() {
overlay.alpha = 1
}
hideOverlay() {
overlay.alpha = 0
}
}
Where this gets complicated, is I'm cutting the shape of the originating WidgetView out of the overlay, so that its controls are still visible underneath. This works fine:
class CutoutView: UIView {
var holes: [CGRect]?
convenience init(holes: [CGRect], backgroundColor: UIColor?) {
self.init()
self.holes = holes
self.backgroundColor = backgroundColor ?? UIColor.black.withAlphaComponent(0.5)
isOpaque = false
}
override func draw(_ rect: CGRect) {
backgroundColor?.setFill()
UIRectFill(rect)
guard let rectsArray = holes else {
return
}
for holeRect in rectsArray {
let holeRectIntersection = rect.intersection(holeRect)
UIColor.clear.setFill()
UIRectFill(holeRectIntersection)
}
}
}
... except the problem:
Touches aren't forwarded through the cutout hole. So I thought I'd be clever, and use this extension to determine whether the pixels at the touch point are transparent or not, but I can't even get that far, because hitTest() and point(inside, with event) don't respond to touches outside of the WidgetView's frame.
The way I can see it, there are four (potential) ways to solve this, but I can't get any of them working.
Find some magical (🦄) way to to make hitTest or point(inside) respond anywhere in the keyWindow, or at least the overlayView's frame
Add a UITapGestureRecognizer to the overlayView and forward the appropriate touches to the originating view controller (this partially works — the tap gesture responds, but I don't know where to go from there)
Use a delegate/protocol implementation to tell the original WidgetView to respond to touches
Add the overlay and its subviews to a different parent view altogether that isn't the keyWindow?
Below the fold, here is a complete executable setup, which relies on a new single view project with storyboard. It relies on SnapKit constraints, for which you can use the following podfile:
podfile
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target 'YourTarget' do
pod 'SnapKit', '~> 4.2.0'
end
ViewController.swift
import UIKit
import SnapKit
class ViewController: UIViewController {
public var utilityToolbar: UtilityToolbar!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkGray
setup()
}
func setup() {
let button1 = UtilityToolbar.Button(title: "One", buttonPressed: nil)
let button2 = UtilityToolbar.Button(title: "Two", buttonPressed: nil)
let button3 = UtilityToolbar.Button(title: "Three", buttonPressed: nil)
let button4 = UtilityToolbar.Button(title: "Four", buttonPressed: nil)
let button5 = UtilityToolbar.Button(title: "Five", buttonPressed: nil)
let menuItems: [UtilityToolbar.Button] = [button1, button2, button3, button4, button5]
menuItems.forEach({
$0.setTitleColor(#colorLiteral(red: 0.1963312924, green: 0.2092989385, blue: 0.2291107476, alpha: 1), for: .normal)
})
utilityToolbar = UtilityToolbar(title: "One", menuItems: menuItems)
utilityToolbar.titleButton.setTitleColor(#colorLiteral(red: 0.1963312924, green: 0.2092989385, blue: 0.2291107476, alpha: 1), for: .normal)
utilityToolbar.backgroundColor = .white
utilityToolbar.dropdownContainer.backgroundColor = .white
view.addSubview(utilityToolbar)
utilityToolbar.snp.makeConstraints { (make) in
make.left.right.equalToSuperview()
make.top.equalToSuperview().offset(250)
make.height.equalTo(50.0)
}
}
}
CutoutView.swift
import UIKit
class CutoutView: UIView {
var holes: [CGRect]?
convenience init(holes: [CGRect], backgroundColor: UIColor?) {
self.init()
self.holes = holes
self.backgroundColor = backgroundColor ?? UIColor.black.withAlphaComponent(0.5)
isOpaque = false
}
override func draw(_ rect: CGRect) {
backgroundColor?.setFill()
UIRectFill(rect)
guard let rectsArray = holes else { return }
for holeRect in rectsArray {
let holeRectIntersection = rect.intersection(holeRect)
UIColor.clear.setFill()
UIRectFill(holeRectIntersection)
}
}
}
UtilityToolbar.swift
import Foundation import UIKit import SnapKit
class UtilityToolbar: UIView {
class Button: UIButton {
var functionIdentifier: String?
var buttonPressed: (() -> Void)?
fileprivate var owner: UtilityToolbar?
convenience init(title: String, buttonPressed: (() -> Void)?) {
self.init(type: .custom)
self.setTitle(title, for: .normal)
self.functionIdentifier = title.lowercased()
self.buttonPressed = buttonPressed
}
}
enum MenuState {
case open
case closed
}
enum TitleStyle {
case label
case dropdown
}
private(set) public var menuState: MenuState = .closed
var itemHeight: CGFloat = 50.0
var spacing: CGFloat = 6.0 { didSet { dropdownStackView.spacing = spacing } }
var duration: TimeInterval = 0.15
var dropdownContainer: UIView!
var titleButton: UIButton = UIButton()
#IBOutlet weak fileprivate var toolbarStackView: UIStackView!
private var stackViewBottomConstraint: Constraint!
private var dropdownStackView: UIStackView!
private var overlayView: CutoutView!
private var menuItems: [Button] = []
private var expandedHeight: CGFloat { get { return CGFloat(menuItems.count - 1) * itemHeight + (spacing * 2) } }
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
convenience init(title: String, menuItems: [Button]) {
self.init()
self.titleButton.setTitle(title, for: .normal)
self.menuItems = menuItems
commonInit()
}
private func commonInit() {
self.addSubview(titleButton)
titleButton.addTarget(self, action: #selector(titleButtonPressed(_:)), for: .touchUpInside)
titleButton.snp.makeConstraints { $0.edges.equalToSuperview() }
dropdownContainer = UIView()
dropdownStackView = UIStackView()
dropdownStackView.axis = .vertical
dropdownStackView.distribution = .fillEqually
dropdownStackView.alignment = .fill
dropdownStackView.spacing = spacing
dropdownStackView.alpha = 0
dropdownStackView.translatesAutoresizingMaskIntoConstraints = true
menuItems.forEach({
$0.owner = self
$0.addTarget(self, action: #selector(menuButtonPressed(_:)), for: .touchUpInside)
})
}
override func layoutSubviews() {
super.layoutSubviews()
// Block if the view isn't fully ready, or if the containerView has already been added to the window
guard
let keyWindow = UIApplication.shared.keyWindow,
self.globalFrame != .zero,
dropdownContainer.superview == nil else { return }
overlayView = CutoutView(frame: keyWindow.frame)
overlayView.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.5)
overlayView.alpha = 0
overlayView.holes = [self.globalFrame!]
keyWindow.addSubview(overlayView)
keyWindow.addSubview(dropdownContainer)
dropdownContainer.snp.makeConstraints { (make) in
make.left.right.equalToSuperview()
make.top.equalToSuperview().offset((self.globalFrame?.origin.y ?? 0) + self.frame.height)
make.height.equalTo(0)
}
dropdownContainer.addSubview(dropdownStackView)
dropdownStackView.snp.makeConstraints({ (make) in
make.left.right.equalToSuperview().inset(spacing).priority(.required)
make.top.equalToSuperview().priority(.medium)
stackViewBottomConstraint = make.bottom.equalToSuperview().priority(.medium).constraint
})
}
public func openMenu() {
titleButton.isSelected = true
dropdownStackView.addArrangedSubviews(menuItems.filter { $0.titleLabel?.text != titleButton.titleLabel?.text })
dropdownContainer.layoutIfNeeded()
dropdownContainer.snp.updateConstraints { (make) in
make.height.equalTo(self.expandedHeight)
}
stackViewBottomConstraint.update(inset: spacing)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
self.overlayView.alpha = 1
self.dropdownStackView.alpha = 1
self.dropdownContainer.superview?.layoutIfNeeded()
}) { (done) in
self.menuState = .open
}
}
public func closeMenu() {
titleButton.isSelected = false
dropdownContainer.snp.updateConstraints { (make) in
make.height.equalTo(0)
}
stackViewBottomConstraint.update(inset: 0)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
self.overlayView.alpha = 0
self.dropdownStackView.alpha = 0
self.dropdownContainer.superview?.layoutIfNeeded()
}) { (done) in
self.menuState = .closed
self.dropdownStackView.removeAllArrangedSubviews()
}
}
#objc private func titleButtonPressed(_ sender: Button) {
switch menuState {
case .open:
closeMenu()
case .closed:
openMenu()
}
}
#objc private func menuButtonPressed(_ sender: Button) {
closeMenu()
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// Nothing of interest is happening here unless the touch is inside the containerView
print(UIColor.colorOfPoint(point: point, in: overlayView).cgColor.alpha > 0)
if UIColor.colorOfPoint(point: point, in: overlayView).cgColor.alpha > 0 {
return true
}
return super.point(inside: point, with: event)
} }
Extensions.swift
import UIKit
extension UIWindow {
static var topController: UIViewController? {
get {
guard var topController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
}
}
}
public extension UIView {
var globalPoint: CGPoint? {
return self.superview?.convert(self.frame.origin, to: nil)
}
var globalFrame: CGRect? {
return self.superview?.convert(self.frame, to: nil)
}
}
extension UIColor {
static func colorOfPoint(point:CGPoint, in view: UIView) -> UIColor {
var pixel: [CUnsignedChar] = [0, 0, 0, 0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
let context = CGContext(data: &pixel, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
context!.translateBy(x: -point.x, y: -point.y)
view.layer.render(in: context!)
let red: CGFloat = CGFloat(pixel[0]) / 255.0
let green: CGFloat = CGFloat(pixel[1]) / 255.0
let blue: CGFloat = CGFloat(pixel[2]) / 255.0
let alpha: CGFloat = CGFloat(pixel[3]) / 255.0
let color = UIColor(red:red, green: green, blue:blue, alpha:alpha)
return color
}
}
extension UIStackView {
func addArrangedSubviews(_ views: [UIView?]) {
views.filter({$0 != nil}).forEach({ self.addArrangedSubview($0!)})
}
func removeAllArrangedSubviews() {
let removedSubviews = arrangedSubviews.reduce([]) { (allSubviews, subview) -> [UIView] in
self.removeArrangedSubview(subview)
return allSubviews + [subview]
}
// Deactivate all constraints
NSLayoutConstraint.deactivate(removedSubviews.flatMap({ $0.constraints }))
// Remove the views from self
removedSubviews.forEach({ $0.removeFromSuperview() })
}
}
Silly me, I need to put the hitTest on the overlay view (CutoutView) not the calling view.
class CutoutView: UIView {
// ...
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard UIColor.colorOfPoint(point: point, in: self).cgColor.alpha > 0 else { return nil }
return super.hitTest(point, with: event)
}
}

ChildViewController and IBOutlet UIView in SWIFT

I spent days to try to solve this problem but can't find any solution (except programmaticaly):
I have 2 UIViewController where the 2nd is a UIChildViewController.
In the ChildViewController I have an IBOutlet UIView class's attribute linked to a CustomClassUIview in the storyboard.
This CustomClassUIview have methods and attribute to update the shape layer define inside this class.
The problem is when I try to access to one attribute of this custom uiview, it returns nil.
I know that the IBOutlet is fired outside viewDidLoad, viewWillAppear,... but I don't know how to keep alloc.
I did with storyboard to be easier to design but if I did this only programmaticaly it works.
Any help please.
class ChildViewController: UIViewController {
#IBOutlet weak var customUIView: CustomClassUIView!
var upperValueProgress:CGFloat = 0 {
didSet {
self.customUIView.progress = upperValueProgress
updateLayerFrames()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.greenColor()
}
override func viewWillAppear(animated: Bool) {
customUIView.progress = 0
}
func updateLayerFrames() {
customUIView.reveal()
}
}
class FirstViewController: UIViewController {
var customUIViewController:ChildViewController!
override func viewDidLoad() {
super.viewDidLoad()
self.customUIViewController = ChildViewController()
}
...
func update(){
self.customUIViewController.upperValueProgress = 56 // Example
}
,;
#IBDesignable class CustomClassUIview: UIView {
let pathLayer = CAShapeLayer()
var circleRadius: CGFloat {
get {
return self.frame.width / 2
}
set {
}
}
#IBInspectable var progress: CGFloat = 0 {
didSet {
reveal()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
reveal()
}
func configure() {
pathLayer.frame = self.bounds
pathLayer.lineWidth = 2
pathLayer.backgroundColor = UIColor.clearColor().CGColor
pathLayer.fillColor = UIColor.clearColor().CGColor
pathLayer.strokeColor = UIColor.redColor().CGColor
layer.addSublayer(segmentTimerPathLayer)
backgroundColor = UIColor.whiteColor()
progress = 0
}
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: self.bounds.minX, y: self.bounds.minY, width: 2*circleRadius, height: 2*circleRadius)
circleFrame.origin.x = 0
circleFrame.origin.y = 0
return circleFrame
}
func circlePath() -> UIBezierPath {
return UIBezierPath(ovalInRect: circleFrame())
}
override func layoutSubviews() {
super.layoutSubviews()
pathLayer.frame = bounds
pathLayer.path = circlePath().CGPath
}
func reveal() {
println(progress)
}
}

Resources