iOS Swift protocol & delegate - ios

Why my delegation is not working?
With my sample the action button works when clicked, but for some reason it does not reach the didAction function in my second controller.
protocol HomeControllerDelegate: class {
func didAction()
}
class HomeController: UIViewController {
weak var delegate: HomeControllerDelegate?
private let actionButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Action", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .black
button.setHeight(50)
button.setWidth(100)
button.addTarget(self, action: #selector(handleAction), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(actionButton)
actionButton.centerX(inView: view)
actionButton.centerY(inView: view)
}
#objc func handleAction() {
print("DEBUG: Handle Action Button delegate here....")
delegate?.didAction()
}
}
class SecondController: UIViewController {
let homeController = HomeController()
override func viewDidLoad() {
super.viewDidLoad()
homeController.delegate = self
}
}
extension SecondController: HomeControllerDelegate {
func didAction() {
print("DEBUG: In SecondController - didAction()")
}
}

Many thanks for the guidance, it has made me look at the fundamentals and also what to research.
I have created the below which solves my problem.
A container controller that Instantiates two ViewControlers and sets them as childVC's
Then created a protocol on FirstChildVC and SecondChildVC is the delegate
All is working now an I understand a lot better.
class ContainerController: UIViewController {
let secondChildVC = SecondChildVC()
let firstChildVC = FirstChildVC()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
addFirstChildVC()
addSecondChildVC()
}
func addSecondChildVC(){
addChild(secondChildVC)
view.addSubview(secondChildVC.view)
secondChildVC.didMove(toParent: self)
setSecondChildVCConstraints()
}
func addFirstChildVC(){
addChild(firstChildVC)
view.addSubview(firstChildVC.view)
firstChildVC.delegateActionButton = secondChildVC
firstChildVC.didMove(toParent: self)
setFirstChildVCConstraints()
}
func setFirstChildVCConstraints() {
firstChildVC.view.translatesAutoresizingMaskIntoConstraints = false
firstChildVC.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
firstChildVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
firstChildVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
firstChildVC.view.heightAnchor.constraint(equalToConstant: 200).isActive = true
}
func setSecondChildVCConstraints() {
secondChildVC.view.translatesAutoresizingMaskIntoConstraints = false
secondChildVC.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true
secondChildVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
secondChildVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
secondChildVC.view.heightAnchor.constraint(equalToConstant: 200).isActive = true
}
}
protocol FirstChildVCDelegate: class {
func didAction(data: String)
}
class FirstChildVC: UIViewController {
weak var delegateActionButton: FirstChildVCDelegate!
private let actionButton: UIButton = {
let button = UIButton(type: .system)
let buttonHeight = 30
button.setTitle("Button", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .black
button.layer.cornerRadius = CGFloat(buttonHeight / 2)
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor.constraint(equalToConstant: CGFloat(buttonHeight)).isActive = true
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.addTarget(self, action: #selector(handleAction), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
configureActionButtonView()
}
func configureActionButtonView() {
view.addSubview(actionButton)
actionButton.translatesAutoresizingMaskIntoConstraints = false
actionButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
actionButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc func handleAction() {
delegateActionButton.didAction(data: "Button Pressed")
}
}
class SecondChildVC: UIViewController {
private let actionButtonLabel: UILabel = {
let label = UILabel()
label.text = "test"
label.font = .systemFont(ofSize: 18)
label.textColor = .white
label.isHidden = true
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPurple
configureActionButtonLabel()
}
func configureActionButtonLabel() {
view.addSubview(actionButtonLabel)
actionButtonLabel.translatesAutoresizingMaskIntoConstraints = false
actionButtonLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
actionButtonLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
extension SecondChildVC: FirstChildVCDelegate {
func didAction(data: String) {
actionButtonLabel.isHidden.toggle()
actionButtonLabel.text = data
}
}
Initial State
State once Button pressed

Related

Show UIView When Popping Back on a Navigation Controller

So I'm trying to show an UIView in a stack view when I click continue on a controller and pop back to the previous controller. However, I'm having trouble showing the view when I pop. It will show if I present the controller, but I need it to show when I pop. How should I go about this? Thank you.
// ServiceDetailController
// MARK: - Properties
lazy var dateContainer: ShadowCardView = {
let view = ShadowCardView()
view.backgroundColor = .white
view.addShadow()
view.setHeight(height: 40)
let stack = UIStackView(arrangedSubviews: [calendarIcon, dateLabel, timeOfDayLabel])
stack.axis = .horizontal
stack.distribution = .fillProportionally
stack.spacing = 8
view.addSubview(stack)
stack.centerY(inView: view)
stack.anchor(left: view.leftAnchor, right: view.rightAnchor, paddingLeft: 12, paddingRight: 70)
view.addSubview(closeButton)
closeButton.centerY(inView: view)
closeButton.anchor(right: view.rightAnchor, paddingRight: 12)
return view
}()
lazy var dateStack = UIStackView(arrangedSubviews: [dateLabelStack, dateContainer, dateContainerView])
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
// MARK: - Selectors
#objc func handleDateCreationTapped() {
let controller = DateCreationController()
controller.jobService = self.jobService
navigationController?.pushViewController(controller, animated: true)
}
// MARK: - Helper Functions
fileprivate func configureUI() {
setupNavigationBar()
view.backgroundColor = .groupTableViewBackground
setupServiceInfoView()
setupFormView()
}
fileprivate func setupFormView() {
showDataContainer(shouldShow: false)
setupTapGestureRecognizers()
}
fileprivate func setupTapGestureRecognizers() {
let dateTap = UITapGestureRecognizer(target: self, action: #selector(handleDateCreationTapped))
dateTap.numberOfTapsRequired = 1
dateTextField.addGestureRecognizer(dateTap)
}
func showDateContainer(shouldShow: Bool) {
if shouldShow {
dateContainer.isHidden = false
dateStack.spacing = 5
} else {
dateContainer.isHidden = true
dateStack.spacing = -18
}
}
// DateCreationController
// MARK: - Properties
private let continueButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Continue", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = UIFont(name: "AvenirNext-Medium", size: 14)
button.backgroundColor = .darkGray
button.setHeight(height: 50)
button.layer.cornerRadius = 8
button.addTarget(self, action: #selector(handleContinue), for: .touchUpInside)
return button
}()
// MARK: - Selectors
#objc func handleContinue() {
let controller = ServiceDetailController()
controller.showDateContainer(shouldShow: true)
navigationController?.popViewController(animated: true)
}
The main problem is this func in your DateCreationController:
#objc func handleContinue() {
// here, you are creating a NEW instance of ServiceDetailController
let controller = ServiceDetailController()
controller.showDateContainer(shouldShow: true)
// here, you are popping back to where you came from... the EXISTING instance of ServiceDetailController
navigationController?.popViewController(animated: true)
}
You need to use either delegate/protocol pattern or a closure.
Here's an example using a closure...
Add this var to your DateCreationController class:
var continueCallback: ((Bool)->())?
In your ServiceDetailController class, when you instantiate and push your DateCreationController, you'll also setup that closure:
#objc func handleDateCreationTapped() {
let controller = DateCreationController()
controller.jobService = self.jobService
// add the closure
controller.continueCallback = { [weak self] shouldShow in
guard let self = self else { return }
self.showDateContainer(shouldShow: shouldShow)
self.navigationController?.popViewController(animated: true)
}
navigationController?.pushViewController(controller, animated: true)
}
Then, in your button action in DateCreationController, you "call back" using that closure:
#objc func handleContinue() {
continueCallback?(true)
}
Here's a runnable example. It creates a simple yellow view as the dateContainer view, but it is hidden on load. Tapping anywhere will push to DateCreationController, which has a single "Continue" button. Tapping that button will execute the closure, where the dateContainer view will be set to visible and we'll pop back:
class ServiceDetailController: UIViewController {
var jobService: String = "abc"
// plain yellow view
let dateContainer: UIView = {
let view = UIView()
view.backgroundColor = .yellow
return view
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
let infoLabel = UILabel()
infoLabel.text = "Tap anywhere"
view.addSubview(infoLabel)
view.addSubview(dateContainer)
infoLabel.translatesAutoresizingMaskIntoConstraints = false
dateContainer.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
infoLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
infoLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
dateContainer.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 20.0),
dateContainer.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
dateContainer.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
dateContainer.heightAnchor.constraint(equalToConstant: 100.0),
])
setupTapGestureRecognizers()
// hide dateContainer on load
showDateContainer(shouldShow: false)
}
// MARK: - Selectors
#objc func handleDateCreationTapped() {
let controller = DateCreationController()
controller.jobService = self.jobService
// add the closure
controller.continueCallback = { [weak self] shouldShow in
guard let self = self else { return }
self.showDateContainer(shouldShow: shouldShow)
self.navigationController?.popViewController(animated: true)
}
navigationController?.pushViewController(controller, animated: true)
}
// MARK: - Helper Functions
fileprivate func setupTapGestureRecognizers() {
let dateTap = UITapGestureRecognizer(target: self, action: #selector(handleDateCreationTapped))
dateTap.numberOfTapsRequired = 1
view.addGestureRecognizer(dateTap)
}
func showDateContainer(shouldShow: Bool) {
if shouldShow {
dateContainer.isHidden = false
} else {
dateContainer.isHidden = true
}
}
}
class DateCreationController: UIViewController {
var jobService: String = "abc"
var continueCallback: ((Bool)->())?
lazy var continueButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Continue", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = UIFont(name: "AvenirNext-Medium", size: 14)
button.backgroundColor = .darkGray
button.layer.cornerRadius = 8
button.addTarget(self, action: #selector(handleContinue), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
view.addSubview(continueButton)
continueButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
continueButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
continueButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
continueButton.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.75),
continueButton.heightAnchor.constraint(equalToConstant: 50.0),
])
}
// MARK: - Selectors
#objc func handleContinue() {
continueCallback?(true)
}
}

Protocol delegate not giving desired result

A simple profile page with an image to display based on what i select on the settings page, different files for controller and view, click on tick of profile go to settings page, select image1 or image2 and that image must display on profile page ,i try and create a protocol on settings to be able to add image , then implement a delegate on profile view file so that it can update the image its not working, can any one please point out my error
SettingsController
import UIKit
protocol ShowImage: class {
func displayImage(_ of: UIImage)
}
class SettingsController: UIViewController {
weak var delegate: ShowImage?
override func viewDidLoad() {
super.viewDidLoad()
let settings = SettingsView()
view.addSubview(settings.view)
settings.btn1.addTarget(self, action: #selector(dCode), for: .touchUpInside)
// Do any additional setup after loading the view.
}
#objc func dCode() {
let image = UIImage(named: "homei")
delegate?.displayImage(image!)
navigationController?.pushViewController(ProfileController(), animated: true)
}
}
SettingsView
import UIKit
class SettingsView: UIViewController {
var btn1 = UIButton()
var btn2 = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = false
view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
view.backgroundColor = UIColor.white
btn1.heightAnchor.constraint(equalToConstant: 30).isActive = true
btn1.widthAnchor.constraint(equalToConstant: 150).isActive = true
btn1.setTitleColor(UIColor.red, for: .normal)
btn1.backgroundColor = UIColor.green
btn2.heightAnchor.constraint(equalToConstant: 30).isActive = true
btn2.widthAnchor.constraint(equalToConstant: 150).isActive = true
btn2.setTitleColor(UIColor.red, for: .normal)
btn2.backgroundColor = UIColor.green
btn1.setTitle("Image1", for: .normal)
btn2.setTitle("Image2", for: .normal)
let stackP = UIStackView()
stackP.axis = .horizontal
stackP.alignment = .top
stackP.spacing = 10
stackP.distribution = .fill
stackP.addArrangedSubview(btn1)
stackP.addArrangedSubview(btn2)
stackP.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackP)
stackP.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackP.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// Do any additional setup after loading the view.
}
}
Profile Controller
import UIKit
class ProfileController: UIViewController {
let profile = ProfileView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(profile.view)
// Do any additional setup after loading the view.
profile.settingsBtn.addTarget(self, action: #selector(gotoSettings), for: .touchUpInside)
}
#objc func gotoSettings(){
let settings = SettingsController()
navigationController?.pushViewController(settings, animated: true)
}
}
Profile View
import UIKit
class ProfileView: UIViewController, ShowImage{
func displayImage(_ of: UIImage) {
apply(img: of)
}
var bgImage = UIImageView()
var settingsBtn = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = false
view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
bgImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(bgImage)
bgImage.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
bgImage.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
settingsBtn.heightAnchor.constraint(equalToConstant: 60).isActive = true
settingsBtn.widthAnchor.constraint(equalToConstant: 60).isActive = true
// settingsBtn.setTitle("Settings", for: .normal)
settingsBtn.setImage(UIImage(named: "tick"), for: .normal)
settingsBtn.backgroundColor = UIColor.red
settingsBtn.layer.cornerRadius = 5
view.addSubview(settingsBtn)
settingsBtn.translatesAutoresizingMaskIntoConstraints = false
settingsBtn.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
settingsBtn.topAnchor.constraint(equalTo: view.topAnchor, constant: 70).isActive = true
let set = SettingsController()
set.delegate = self
// Do any additional setup after loading the view.
}
func apply(img: UIImage)
{
bgImage.image = img
}
}
enter image description here
let set = SettingsController()
set.delegate = self
You're making a brand new SettingsController, setting its delegate... and then doing nothing with it. You probably want to find the existing SettingsController that is on the screen, though you have a very confusing system here with xView and xController, both of which are view controllers, so it's hard to know what's going on.

What error am I making when trying to pass data between ViewControllers using closures?

My main View Controller has an embedded UITabbarController in it. There are 2 subVCs: viewControllerONE & viewControllerTWO.
viewControllerONE displays a label showing the user's current score, and viewControllerTWO displays a button, pressing which should display an error window on viewControllerONE.
(For the sake of simplicity, the user has to manually navigate to viewControllerONE using the tab bar to see the error, ie, pressing the button on viewControllerTWO doesn't take you to viewControllerONE and then display the errorWindow.)
The error window is a simple UIView class.
From SO, I have learnt that the best way to pass data in swift isn't delegates but closures, as the former is more of an objective C design, and so I've used closures to pass data between view controllers.
So here is my viewControllerONE code:
class ViewControllerONE: UIViewController {
var score = 10
lazy var scoreLabel: UILabel = {
let label = UILabel()
label.text = String(score)
label.font = .systemFont(ofSize: 80)
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scoreLabel)
let VC = ViewControllerTWO()
VC.callback = { [weak self ] in
let error = errorView()
self?.view.addSubview(error)
}
NSLayoutConstraint.activate([
scoreLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
scoreLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
}
and here is my viewControllerTWO code:
class ViewControllerTWO: UIViewController {
var callback: (() -> Void)?
let buttonTwo: UIButton = {
let button = UIButton()
button.setTitle("HIT THIS BUTTON!", for: .normal)
button.backgroundColor = .systemBlue
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(buttonTwoPressed), for: .touchUpInside)
button.layer.cornerRadius = 8
return button
}()
#objc func buttonTwoPressed() {
print("PRESSEDDDD")
callback?()
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(buttonTwo)
NSLayoutConstraint.activate([
buttonTwo.centerXAnchor.constraint(equalTo: view.centerXAnchor),
buttonTwo.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
And here is the error view:
class ErrorView: UIView {
fileprivate let dismissButton: UIButton = {
let button = UIButton()
button.setTitle("DISMISS!!!!", for: .normal)
button.backgroundColor = .systemBlue
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(dismissButtonPressed), for: .touchUpInside)
button.layer.cornerRadius = 12
return button
}()
#objc func dismissButtonPressed() {
self.errorGoAway()
}
fileprivate let errorViewBox: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
v.layer.cornerRadius = 24
return v
}()
#objc fileprivate func errorGoAway() {
self.alpha = 0
}
#objc fileprivate func errorShow() {
self.alpha = 1
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(errorGoAway)))
self.backgroundColor = UIColor.gray
self.backgroundColor?.withAlphaComponent(0.8)
self.frame = UIScreen.main.bounds
self.addSubview(errorViewBox)
errorViewBox.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
errorViewBox.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
errorViewBox.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.7).isActive = true
errorViewBox.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.45).isActive = true
errorViewBox.addSubview(dismissButton)
dismissButton.leadingAnchor.constraint(equalTo: errorViewBox.leadingAnchor).isActive = true
dismissButton.trailingAnchor.constraint(equalTo: errorViewBox.trailingAnchor).isActive = true
dismissButton.centerYAnchor.constraint(equalTo: errorViewBox.centerYAnchor).isActive = true
dismissButton.heightAnchor.constraint(equalTo: errorViewBox.heightAnchor, multiplier: 0.15).isActive = true
errorShow()
}
You need to get your controller from tabBarController instead of create new instance:
class ViewControllerONE: UIViewController {
var score = 10
lazy var scoreLabel: UILabel = {
let label = UILabel()
label.text = String(score)
label.font = .systemFont(ofSize: 80)
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.textColor = .blue
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scoreLabel)
var VC: ViewControllerTWO?
let arrayOfVC = self.tabBarController?.viewControllers ?? []
for item in arrayOfVC {
if let secondVC = item as? ViewControllerTWO {
VC = secondVC
break
}
}
VC?.callback = { [weak self ] in
let error = ErrorView()
self?.view.addSubview(error)
}
NSLayoutConstraint.activate([
scoreLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
scoreLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
}

Interacting with contents of UIPopoverPresentationController on regular sized iPhones?

It appears - at least by default, that while you can interact with the contents of a popover on a plus iPhone and dismiss it by tapping on the background, on a regular, non plus phone, the behavior is the opposite.
Does anyone know how to correct and/or configure this so that you can interact with a popover on an regular iPhone?
I have a sample that demonstrates the problem here:
https://github.com/chrisco314/iPhone-Popover-Test,
but the relevant code is:
class PresentingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
configure()
}
func configure() {
view.addSubview(button)
view.backgroundColor = .white
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.topAnchor.constraint(equalTo: view.topAnchor, constant: 50).isActive = true
}
lazy var button: UIButton = {
let button = UIButton()
button.layer.cornerRadius = 10
button.contentEdgeInsets = .init(top: 8, left: 8, bottom: 8, right: 8)
button.backgroundColor = .blue
button.setTitle("Show popover", for: .normal)
button.addTarget(self, action: #selector(didTap(sender:)), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#objc func didTap(sender: UIButton) {
let presented = PresentedViewController()
presented.modalPresentationStyle = .popover
let popover = presented.popoverPresentationController!
popover.delegate = self
popover.sourceRect = sender.bounds
popover.sourceView = sender
popover.permittedArrowDirections = .up
popover.backgroundColor = popover.presentedViewController.view.backgroundColor
self.present(presented, animated: true, completion: {})
}
}
extension PresentingViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
class PresentedViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
configure()
}
func configure() {
view.backgroundColor = .green
view.addSubview(text)
text.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
view.rightAnchor.constraint(equalTo: text.rightAnchor, constant: 20).isActive = true
text.topAnchor.constraint(equalTo: view.topAnchor, constant: 50).isActive = true
view.bottomAnchor.constraint(greaterThanOrEqualTo: text.bottomAnchor, constant: 50).isActive = true
}
lazy var text: UITextField = {
let view = UITextField()
view.text = "Placeholder"
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .blue
return view
}()
}
Thanks!
I am thinking now that this might have been a simulator problem. I had seen this on my main project, a separate sample, and then wrote a new cleaner sample for public consumption. I then switched back and forth between different versions of the simulator and it started working again.
Weird.

Swift: Screen Freezing from time to time when Dismissing a ViewController displaying a picture over the current context

I am having some troubles with: The screen freezing from time to time when Dismissing the ViewController displaying a picture over the current context.
May someone provide me some insights on how to fix this problem?
A sample of my codes is found below:
import UIKit
class ViewControllerCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
addSubview(showPhotoButton)
showPhotoButton.leftAnchor.constraint(equalTo: leftAnchor, constant: 200).isActive = true
showPhotoButton.bottomAnchor.constraint(equalTo: topAnchor, constant: 160).isActive = true
showPhotoButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
showPhotoButton.widthAnchor.constraint(equalToConstant: 70).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var showPhotoButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Show", for: .normal)
button.addTarget(self, action: #selector(showSale), for: .touchUpInside)
button.setTitleColor(UIColor(r: 120, g: 80, b: 255), for: .normal)
return button
}()
#objc func showSale() {
let popupViewController = PopupViewController()
popupViewController.modalPresentationStyle = .overCurrentContext
popupViewController.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
window!.rootViewController?.present(PopupViewController, animated: true, completion: nil)
}
}
import UIKit
class SalePopupViewController: UIViewController {
override func viewDidLoad() {
view.backgroundColor = UIColor(white: 1, alpha: 0.60)
view.isOpaque = false
view.addSubview(rebateImage)
view.addSubview(dismissButton)
dismissButton.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
dismissButton.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
dismissButton.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
dismissButton.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
rebateImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
rebateImage.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -35).isActive = true
rebateImage.heightAnchor.constraint(equalToConstant: 290).isActive = true
rebateImage.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1).isActive = true
}
let dismissButton: UIButton = {
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(dismissPopup), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
let rebateImage: UIImageView = {
let image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.layer.masksToBounds = false
image.layer.cornerRadius = 2
image.contentMode = .scaleAspectFill
image.clipsToBounds = true
image.image = UIImage(named: "SaleCostco")
return image
}()
#objc func dismissPopup() {
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.main.async {
self.dismiss(animated: true, completion: nil)
}
}
}
}
Your asynchronous code doesn't make sense. You dispatch onto a global queue and then immediately back onto the main thread. Try simply changing the implementation of dismissPopup() to this:
#objc func dismissPopup() {
dismiss(animated: true, completion: nil)
}

Resources