I've used some code from a YouTube video (Custom Alerts in Swift 5 (Xcode 11) - 2020 iOS App by IOS Academy) to create a custom alert in swift that enters the scene from the top of an iPhone screen, stops in the middle of the screen, then when the user presses the OK button the alert exits the screen by sliding down toward the bottom of the scene.
The Issue: The issue is that when the code displays the first alert everything seems fine, however if a second or third different alert is displayed, it seems that the first alert subview (or 2nd / 3rd etc) isn't being removed properly from the superview, meaning that when the 2nd/3rd alert is being displayed it is being displayed over the top of the previous alert.
I am using Xcode v13.0. I suspect the reason its happening is because the removeFromSuperview
is happening in an ObjC function which is outside of where the subviews were created. However, I don't know how to solve this. I have tried many different ways and searched high and low for similar examples.
View Controller code: Here is the code for my view controller:
import UIKit
class ViewController: UIViewController {
var myCounter : Int = 0
@IBOutlet weak var myButton: UIButton!
let customAlert = MyAlert()
override func viewDidLoad() {
super.viewDidLoad()
myButton.backgroundColor = .link
myButton.setTitleColor(.white, for: .normal)
myButton.setTitle("Show Alert", for: .normal)
}
@IBAction func didTapButton(_ sender: Any) {
if myCounter > 3 {
myCounter = 1
}
if myCounter == 1 {
customAlert.showMySomethingAlert(on: self)
}
if myCounter == 2 {
customAlert.showMySomethingAlert2(on: self)
}
if myCounter == 3 {
customAlert.showMySomethingAlert3(on: self)
}
myCounter = myCounter + 1
}
@objc func dismissAlert() {
customAlert.dismissAlert()
}
}
CustomAlert Swift File:
import Foundation
import UIKit
class MyAlert {
struct Constants {
static let backgroundAlphaTo: CGFloat = 0.6
}
// MARK: Define the greying out of the scene
private let backgroundView: UIView = {
let backgroundView = UIView()
backgroundView.backgroundColor = .black
backgroundView.alpha = 0
return backgroundView
}()
// MARK: Define the Alert frame
private let alertView: UIView = {
let alert = UIView()
alert.backgroundColor = .white
alert.layer.masksToBounds = true
alert.layer.cornerRadius = 4
return alert
}()
private let myView: UIView = {
let myView = UIView()
myView.translatesAutoresizingMaskIntoConstraints = false
return myView
}()
private var myTargetView: UIView?
// MARK: Show the alert on the scene
func showAlert(with title: String,
message: String,
on viewController: UIViewController) {
guard let targetView = viewController.view else {
return
}
// MARK: Determine size of frame needed
var numberOfLines = message.count / 40
print("Number of lines is \(numberOfLines)")
var heightOfAlert : CGFloat = 100.0
heightOfAlert = heightOfAlert + (CGFloat(numberOfLines * 25))
// MARK: Attributes for the frame
myTargetView = targetView
backgroundView.frame = targetView.bounds
targetView.addSubview(backgroundView)
targetView.addSubview(alertView)
alertView.frame = CGRect(x: 40,
y: -300,
width: targetView.frame.size.width-80,
height: heightOfAlert)
// ***********************************************
// MARK: Attributes for image within the alert
let myImageButton = UIButton(frame: CGRect(x: 20, y: 20, width: 50, height: 50))
myImageButton.setImage(UIImage(named:"HarbourBridge750x390.png"), for: .normal)
alertView.addSubview(myImageButton)
// ***********************************************
// MARK: Attributes for the title within the alert
let titleLabel = UILabel(frame: CGRect(x: 0,
y: 0,
width: alertView.frame.size.width,
height: 80))
titleLabel.text = title
titleLabel.textAlignment = .center
//titleLabel.textColor = .black
titleLabel.textColor = UIColor(red: 33.00/255, green: 150.00/255, blue: 243.00/255, alpha: 1.00)
titleLabel.font = UIFont.boldSystemFont(ofSize: 12.00)
alertView.addSubview(titleLabel)
// ***********************************************
// MARK: Attributes for the message label within the alert
let messageLabel = UILabel(frame: CGRect(x: 15,
y: 80,
width: alertView.frame.size.width-30,
height: (CGFloat(numberOfLines * 25))))
messageLabel.numberOfLines = 0
messageLabel.text = message
messageLabel.textAlignment = .left
messageLabel.textColor = .black
messageLabel.font = UIFont.systemFont(ofSize: 12.00)
alertView.addSubview(messageLabel)
// ***********************************************
// MARK: Attributes for the Dismiss button within the alert
let button = UIButton(frame: CGRect(x: 0,
y: alertView.frame.size.height-50,
width: alertView.frame.size.width,
height: 50))
button.setTitle("Dismiss", for: .normal)
button.setTitleColor(.link, for: .normal)
button.addTarget(self,
action: #selector(dismissAlert),
for: .touchUpInside)
alertView.addSubview(button)
// MARK: Attributes for the animation
UIView.animate(withDuration: 0.25,
animations: {
self.backgroundView.alpha = Constants.backgroundAlphaTo
}, completion: { done in
if done {
UIView.animate(withDuration: 0.25, animations: {
self.alertView.center = targetView.center
})
}
})
}
// MARK: Dismiss the Alert
@objc func dismissAlert() {
guard let targetView = myTargetView else {
return
}
UIView.animate(withDuration: 0.25,
animations: {
self.alertView.frame = CGRect(x: 40,
y: targetView.frame.size.height,
width: targetView.frame.size.width-80,
height: 300)
}, completion: { done in
if done {
UIView.animate(withDuration: 0.25, animations: {
self.backgroundView.alpha = 0
}, completion: { done in
if done {
self.alertView.removeFromSuperview()
self.backgroundView.removeFromSuperview()
}
})
}
})
}
// MARK: Setup the alerts to keep the code tidy within the main body files
func showMySomethingAlert(on vc: UIViewController) {
showAlert(with: "My Something Alert", message: "Something has gone wrong and your payment was not successful. This app will continue to operate in trial mode.", on: vc)
}
func showMySomethingAlert2(on vc: UIViewController) {
showAlert(with: "My Something Alert", message: "This is a completely different alert and the text should be totally different and not overlap the previous alert whihc appears to be wht is happening", on: vc)
}
func showMySomethingAlert3(on vc: UIViewController) {
showAlert(with: "My Something Alert", message: "THIS IS YOUR THIRD ALERT AND is a mixture of lower and UPPER CASE CHARACTERS.", on: vc)
}
}
Your problem is inside your MyAlert
class, you are reusing the instance of alertView
, but every time you added new myImageButton
titleLabel
messageLabel
and button
First time you called customAlert.showMySomethingAlert(on: self)
you have
alertView1
//Childs
myImageButton1
titleLabel1
messageLabel1
button1
when you called customAlert.showMySomethingAlert2(on: self)
you will have
alertView1
//Childs
myImageButton1
titleLabel1
messageLabel1
button1
//Second instances
myImageButton2
titleLabel2
messageLabel2
button2
And first added view are still there, so you need to remove old ones or will be better to reuse them.
Remove ex. NOT RECOMENDED if the alert always has the same views After removing the alert view from its superview you need to do this.
self.alertView.removeFromSuperview()
//Add this to remove child views
for subview in self.alertView.subviews {
subview.removeFromSuperview()
}
If your alert always will have the save views, will be better to set new texts and image to already created views and not to create new one and add every time showAlert
called.