I have a custom keyboard project with a Storyboard containing a collectionView and a scrollView. I also have a textField for a search bar that pulls up a nib containing a keyboard layout that I can use to search within the keyboard. When the subview containing the keyboard becomes active, I cannot press any of the buttons. I have tried bringing the subView to front upon becoming active and have tried sending the main view to the back when hiding the collectionView. What can I do to use the buttons in the subview?
Before viewDidLoad
let filename = "buttonClick"
let ext = "m4a"
var soundId: SystemSoundID = 0
#objc func typeKey(sender : UIButton)
{
AudioServicesPlaySystemSound(soundId)
self.key.txtSearch.text = "\(self.key.txtSearch.text!)\((sender.titleLabel?.text!)!)"
if(self.key.txtSearch.text != "")
{
isCaps = false
updateKeyBoard()
}
}
#objc func typeSpaceKey(sender : UIButton)
{
AudioServicesPlaySystemSound(soundId)
self.key.txtSearch.text = "\(self.key.txtSearch.text!) "
}
#objc func typeDoneKey(sender : UIButton)
{
AudioServicesPlaySystemSound(soundId)
hideTextField()
}
#objc func typeNumKey(sender : UIButton)
{
AudioServicesPlaySystemSound(soundId)
isNum = !isNum
updateKeyBoard()
// self.key.txtSearch.text = "\(self.key.txtSearch.text!)\((sender.titleLabel?.text!)!)"
}
#objc func typeBackSpaceKey(sender : UIButton)
{
AudioServicesPlaySystemSound(soundId)
self.key.txtSearch.text = "\(self.key.txtSearch.text!.dropLast())"
if(self.key.txtSearch.text == "")
{
isCaps = true
updateKeyBoard()
}
}
#objc func typeCapsKey(sender : UIButton)
{
AudioServicesPlaySystemSound(soundId)
if(isNum)
{
isFirst = !isFirst
}
else
{
isCaps = !isCaps
}
updateKeyBoard()
}
#objc func updateKeyBoard()
{
if(isCaps)
{
self.key.btnCap.setImage(#imageLiteral(resourceName: "cap_F"), for: .normal)
}
else
{
self.key.btnCap.setImage(#imageLiteral(resourceName: "caps"), for: .normal)
}
var count = 0
for btn in buttons
{
if(!isNum)
{
if(isCaps)
{
btn.setTitle("\(arrCapOn[count])", for: .normal)
}
else
{
btn.setTitle("\(arrCapOff[count])", for: .normal)
}
}
else
{
if(isFirst)
{
btn.setTitle("\(arrNumCapOn[count])", for: .normal)
}
else
{
btn.setTitle("\(arrNumCapOff[count])", for: .normal)
}
}
count = count + 1
}
}
#objc func activeTextField()
{
self.key.btnBack.isHidden = false
self.key.txtSearch.becomeFirstResponder()
self.key.constLeftAchor.constant = 40
self.key.constSideKeyboard.constant = 8
self.key.constLeftAchorView.constant = 32
UIView.animate(withDuration: 0.2) {
self.key.viewKeyboard.transform = CGAffineTransform(translationX: 0, y: 0)
self.key.btnBack.alpha = 1.0
self.key.layoutIfNeeded()
}
self.key.btnTextFieldSelect.isHidden = true
}
#objc func hideTextField()
{
self.key.btnBack.isHidden = true
self.key.txtSearch.resignFirstResponder()
self.key.btnTextFieldSelect.isHidden = false
self.key.constSideKeyboard.constant = 400
self.key.constLeftAchor.constant = 24
self.key.constLeftAchorView.constant = 16
UIView.animate(withDuration: 0.2) {
self.key.viewKeyboard.transform = CGAffineTransform(translationX: self.view.frame.width, y: 0)
self.key.btnBack.alpha = 0.0
self.key.layoutIfNeeded()
}
}
#objc func hideArticles(){
self.categoriesScrollView.isHidden = true
self.collectionview.isHidden = true
//self.collectionview.isUserInteractionEnabled = false
//self.view.bringSubview(toFront: key)
//self.key.isUserInteractionEnabled = true
}
#objc func showArticles(){
self.categoriesScrollView.isHidden = false
self.collectionview.isHidden = false
}
#objc func handleTap(_ sender: UITapGestureRecognizer) {
self.key.txtSearch.text = "Hello"
self.inputView?.resignFirstResponder()
print("Hello World")
}
func textFieldDidBeginEditing(_ textField: UITextField) {
}
override func textWillChange(_ textInput: UITextInput?) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(_ textInput: UITextInput?) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
let proxy = self.textDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.dark {
textColor = UIColor.white
} else {
textColor = UIColor.black
}
// self.nextKeyboardButton.setTitleColor(textColor, for: [])
}
after viewDidLoad
if let soundUrl = Bundle.main.url(forResource: filename, withExtension: ext) {
AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundId)
}
self.key = Bundle.main.loadNibNamed("keyboard", owner: nil, options: nil)![0] as! keyboard
self.key.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 160)
//self.key.txtSearch.isUserInteractionEnabled = false
self.key.constLeftAchor.constant = 24
// self.key.viewKeyboard.transform = CGAffineTransform(translationX: self.view.frame.width, y: 0)
self.key.btnBack.isHidden = true
self.key.btnBack.alpha = 0.0
self.key.btnTextFieldSelect.addTarget(self, action: #selector(activeTextField), for: .touchUpInside)
self.key.btnTextFieldSelect.addTarget(self, action: #selector(hideArticles), for: .touchUpInside)
self.key.btnBack.addTarget(self, action: #selector(hideTextField), for: .touchUpInside)
self.key.btnBack.addTarget(self, action: #selector(showArticles), for: .touchUpInside)
buttons.append(self.key.btnQ)
buttons.append(self.key.btnW)
buttons.append(self.key.btnE)
buttons.append(self.key.btnR)
buttons.append(self.key.btnT)
buttons.append(self.key.btnY)
buttons.append(self.key.btnU)
buttons.append(self.key.btnI)
buttons.append(self.key.btnO)
buttons.append(self.key.btnP)
buttons.append(self.key.btnA)
buttons.append(self.key.btnS)
buttons.append(self.key.btnD)
buttons.append(self.key.btnF)
buttons.append(self.key.btnG)
buttons.append(self.key.btnH)
buttons.append(self.key.btnJ)
buttons.append(self.key.btnK)
buttons.append(self.key.btnL)
buttons.append(self.key.btnZ)
buttons.append(self.key.btnX)
buttons.append(self.key.btnC)
buttons.append(self.key.btnV)
buttons.append(self.key.btnB)
buttons.append(self.key.btnN)
buttons.append(self.key.btnM)
for btn in buttons
{
btn.addTarget(self, action: #selector(typeKey), for: .touchUpInside)
}
self.key.btnCap.addTarget(self, action: #selector(typeCapsKey), for: .touchUpInside)
self.key.btnBackSpace.addTarget(self, action: #selector(typeBackSpaceKey), for: .touchUpInside)
self.key.btnSpace.addTarget(self, action: #selector(typeSpaceKey), for: .touchUpInside)
self.key.btnDone.addTarget(self, action: #selector(typeDoneKey), for: .touchUpInside)
//self.key.btnDone.addTarget(self, action: #selector(fetchSearch), for: .touchUpInside)
self.key.btnNum.addTarget(self, action: #selector(typeNumKey), for: .touchUpInside)
view.addSubview(key)
activeTextField()
hideTextField()
Please make sure the added subviews are userinteration enabled and it is not send to back in superview.
Related
I have a design in which the sign-out button is placed at the bottom and should not scroll, side menu has a tableview controller in which we can add rows but my requirement is to add the sign-out button at the bottom. I have tried by adding the sign-out button in the footer view to side menu tableview controller, but showing just bellow the rows which I don't want.
import UIKit
import SideMenu
class ProfileViewController: UIViewController {
var menu:SideMenuNavigationController?
override func viewDidLoad() {
super.viewDidLoad()
configureSideMenu()
}
func configureSideMenu() {
menu = SideMenuNavigationController(rootViewController: MenuListController())
menu?.navigationBar.setBackgroundImage(UIImage(named: "top_navbrBG"), for: .default)
let firstFrame = CGRect(x: 20, y: 0, width: menu?.navigationBar.frame.width ?? 0/2, height: menu?.navigationBar.frame.height ?? 0)
let firstLabel = UILabel(frame: firstFrame)
firstLabel.text = "Settings"
menu?.navigationBar.addSubview(firstLabel)
SideMenuManager.default.addPanGestureToPresent(toView: self.view)
let screenSize = UIScreen.main.bounds
let screenHeight = screenSize.height + 40
let leftBorderView = UIView(frame: CGRect(x: 1, y: -40, width: 1, height: screenHeight))
leftBorderView.backgroundColor = UIColor.init(hexString: "#cfcfcf")
menu?.navigationBar.addSubview(leftBorderView)
}
#IBAction func menuButtonAction(_ sender: UIButton) {
if let menu = menu {
present(menu, animated: true)
}
}
}
// functions
extension ProfileViewController {
#objc func signOutButtonTapped(_ sender: AnyObject?) {
print("sigin out")
}
func getSignOutButton()->UIButton {
let button = UIButton()
button.setTitle("Sign out", for: .normal)
let color = UIColor.init(hexString: "#1A73E9")
button.setTitleColor(color, for: .normal)
button.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)
return button
}
func setConstraintsForSignOutButton(button: UIButton) {
let screenSize = UIScreen.main.bounds
let screenHeight = screenSize.height
guard let menuTopAnchor = menu?.navigationBar.topAnchor else { return }
guard let menuLeadingAnchor = menu?.navigationBar.leadingAnchor else { return }
guard let menuTrailingAnchor = menu?.navigationBar.trailingAnchor else { return }
guard let menuWidth = menu?.menuWidth else { return }
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.leadingAnchor.constraint(equalTo: menuLeadingAnchor, constant: 0),
button.trailingAnchor.constraint(equalTo: menuTrailingAnchor, constant: 0),
button.widthAnchor.constraint(equalToConstant: menuWidth),
button.heightAnchor.constraint(equalToConstant: 40),
button.bottomAnchor.constraint(equalTo: menuTopAnchor, constant: 300)
])
}
}
// Menu Items
class MenuListController: UITableViewController {
var menuItems = [[String: String]]()
override func viewDidLoad() {
super.viewDidLoad()
menuItems.append(["name": "Privacy", "img": "privacy", "key" : "privacy"])
menuItems.append(["name": "Report issue", "img": "report_issue", "key": "report_issue"])
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.tableFooterView = getSignOutButton()
}
#objc func signOutButtonTapped(_ sender: AnyObject?) {
print("sigin out")
}
func getSignOutButton()->UIButton {
let button = UIButton()
button.height = 20
button.width = 100
button.setTitle("Sign out", for: .normal)
let color = UIColor.init(hexString: "#1A73E9")
button.setTitleColor(color, for: .normal)
button.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)
return button
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
menuItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if menuItems.indices.contains(indexPath.row) {
let dict = menuItems[indexPath.row]
cell.textLabel?.text = dict["name"]
if let img = dict["img"] {
cell.imageView?.image = UIImage(named: img)
}
}
return cell
}
}
desired design
Replace this "tableView.tableFooterView = getSignOutButton()" with
self.getSignOutButton() in viewDidLoad.
Next replace your function "getSignOutButton()->UIButton {}" with below code.
func getSignOutButton() {
let button = UIButton()
button.setTitle("Sign out", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor),
button.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor),
button.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor,constant: -20),
button.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
])
}
I need to be able to tap out of the keyboard when not tapping in uitextfield or when not tapping show/hide password button.
I was previously using this code to do that:
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
But the problem with that was that it clicks out of the keyboard even when clicking the show/hide password eye icon. The code I'm using for the show/hide icon is this:
extension UITextField {
func showhidepasswordbutton(image: UIImage = UIImage(systemName: "eye.slash")!) {
let button = UIButton(type: .custom)
button.setImage(image, for: .normal)
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -16, bottom: 0, right: 0)
button.frame = CGRect(x: CGFloat(self.frame.size.width - 25), y: CGFloat(5), width: CGFloat(25), height: CGFloat(25))
button.addTarget(self, action: #selector(self.refreshforshowhide), for: .touchUpInside)
button.tintColor = .darkGray
self.rightView = button
self.rightViewMode = .always
}
#IBAction func refreshforshowhide(_ sender: Any) {
print("ok")
if self.isSecureTextEntry == true {
self.togglePasswordVisibility()
showhidepasswordbutton(image: UIImage(systemName: "eye")!)
} else if self.isSecureTextEntry == false {
self.togglePasswordVisibility()
showhidepasswordbutton(image: UIImage(systemName: "eye.slash")!)
}
}
func togglePasswordVisibility() {
let temptext = self.text
isSecureTextEntry.toggle()
self.text = ""
self.text = temptext
}
}
Sorry for the messy code, just wrote the show/hide password code up.
You could exclude the taps on subviews using gestureRecognizer(_:, shouldReceive:) method in UIGestureRecognizerDelegate.
extension UIViewController: UIGestureRecognizerDelegate {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
tap.delegate = self
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
touch.view?.isDescendant(of: view) == false // will return false if touch was received by a subview
}
}
Update: You could use touch.view == view instead of touch.view?.isDescendant(of: view) == false.
The picture above has two buttons.
The background is filled in red with the left button pressed.
If I press right button here
I want the background of the right button to be filled with red, the left button to be white as the right button, and the button to be deactivated.
override func viewDidLoad() {
super.viewDidLoad()
bookTitleFilterBtn.addTarget(self, action: #selector(bookTitleFilterBtnClicked(_:)), for: .touchUpInside)
authorNameFilterBtn.addTarget(self, action: #selector(authorNameFilterBtnClicked(_:)), for: .touchUpInside)
}
//left button
#objc func bookTitleFilterBtnClicked(_ sender: UIButton) {
DispatchQueue.main.async {
if self.isHighlighted == false {
sender.backgroundColor = .red
let title = NSAttributedString(string: "제목", attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
sender.setAttributedTitle(title, for: .normal)
sender.isHighlighted = true
self.isHighlighted = true
} else {
sender.backgroundColor = .white
let title = NSAttributedString(string: "제목", attributes: [NSAttributedString.Key.foregroundColor: UIColor.black])
sender.setAttributedTitle(title, for: .normal)
sender.isHighlighted = false
self.isHighlighted = false
}
}
}
//right button
#objc func authorNameFilterBtnClicked(_ sender: UIButton) {
DispatchQueue.main.async {
if self.isHighlighted == false {
sender.isHighlighted = true
let title = NSAttributedString(string: "작가", attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
sender.setAttributedTitle(title, for: .normal)
sender.backgroundColor = .red
self.isHighlighted = true
} else {
sender.isHighlighted = false
self.isHighlighted = false
let title = NSAttributedString(string: "작가", attributes: [NSAttributedString.Key.foregroundColor: UIColor.black])
sender.setAttributedTitle(title, for: .normal)
sender.backgroundColor = .white
}
}
}
You forgot to change the backgroundColor in the first condition of the first method. To prevent more of these kind of issues, try to define the logic in a function and call it anywhere you need instead of rewriting it over and over:
override func viewDidLoad() {
super.viewDidLoad()
bookTitleFilterBtn.addTarget(self, action: #selector(buttonClicked(_:)), for: .touchUpInside)
authorNameFilterBtn.addTarget(self, action: #selector(buttonClicked(_:)), for: .touchUpInside)
}
var buttons: [UIButton] { return [bookTitleFilterBtn, authorNameFilterBtn] }
func updateButtonsAppearance(allButtons: [UIButton], selectedButton: UIButton) {
for button in allButtons {
let isSelected = button == selectedButton
let currentTitle = button.currentTitle ?? "-"
let title = NSAttributedString(string: currentTitle, attributes: [.foregroundColor: isSelected ? UIColor.white : UIColor.black])
button.setAttributedTitle(title, for: .normal)
button.backgroundColor = isSelected ? .red : .white
button.isHighlighted = isSelected
}
}
#objc func buttonClicked(_ sender: UIButton) {
DispatchQueue.main.async {
self.updateButtonsAppearance(allButtons: buttons, selectedButton: sender)
}
}
Note that both buttons are now calling same function. So there is only one source of truth now. If it works somewhere, it works everywhere.
You are missing following line of code to change backgroundColor of another button. Add following line of code.
//left button
#objc func bookTitleFilterBtnClicked(_ sender: UIButton) {
DispatchQueue.main.async {
if self.isHighlighted == false {
....
....
authorNameFilterBtn.backgroundColor = .white
} else {
....
}
}
}
//right button
#objc func authorNameFilterBtnClicked(_ sender: UIButton) {
DispatchQueue.main.async {
if self.isHighlighted == false {
....
bookTitleFilterBtn.backgroundColor = .white
} else {
....
}
}
}
This code will change color of buttons vice-versa
set left button default selected from StoryBoard
var selectedButton:String = "" // gloable variable
//left button
#objc func bookTitleFilterBtnClicked(_ sender: UIButton) {
if selectedButton != "제목"
{
selectedButton = "제목"
sender.backgroundColor = .red
let title = NSAttributedString(string: "제목", attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])//title
sender.setAttributedTitle(title, for: .normal)
sender.isHighlighted = true
self.isHighlighted = true
authorNameFilterBtn.backgroundColor = .white
let title1 = NSAttributedString(string: "작가", attributes: [NSAttributedString.Key.foregroundColor: UIColor.black])//title
authorNameFilterBtn.setAttributedTitle(title1, for: .normal)
authorNameFilterBtn.isHighlighted = false
self.isHighlighted = false
}
}
//right button
#objc func authorNameFilterBtnClicked(_ sender: UIButton) {
if selectedButton != "작가"
{
selectedButton = "작가"
sender.isHighlighted = true
let title = NSAttributedString(string: "작가", attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])//Author
sender.setAttributedTitle(title, for: .normal)
sender.backgroundColor = .red
self.isHighlighted = true
bookTitleFilterBtn.isHighlighted = false
self.isHighlighted = false
let title1 = NSAttributedString(string: "제목", attributes: [NSAttributedString.Key.foregroundColor: UIColor.black])
bookTitleFilterBtn.setAttributedTitle(title1, for: .normal)
bookTitleFilterBtn.backgroundColor = .white
}
I managed to show a custom clearbutton, the problem is that it will not be removed when clicking anywhere else or clicking other textfield. It is always showing.
Here is my extension:
extension UITextField {
func clearButtonWithImage(_ image: UIImage) {
let clearButton = UIButton()
clearButton.setImage(image, for: .normal)
clearButton.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
clearButton.contentMode = .scaleAspectFit
clearButton.addTarget(self, action: #selector(self.clear(sender:)), for: .touchUpInside)
self.rightView = clearButton
self.rightViewMode = .always
}
func clear(sender: AnyObject) {
self.text = ""
}
}
and here i show the clearbutton on the method:
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if textField == dateSearchTextField {
self.performSegue(withIdentifier: "showCalendar", sender: self)
textField.clearButtonWithImage(#imageLiteral(resourceName: "icClear"))
return false
} else if textField == timeSearchTextField {
self.performSegue(withIdentifier: "showTimePicker", sender: self)
textField.clearButtonWithImage(#imageLiteral(resourceName: "icClear"))
return false
}
return true
}
I want it to be visible only when clicking inside the textfield.
Replace this:
self.rightViewMode = .always
With:
self.rightViewMode = .whileEditing
I have edited a pop up extension I found on github to the following
PopUpExtension.swift :
import UIKit
extension (Home)
{
//Call this method in view did load
func PopUpInit()
{
createOverlay()
createAlert()
}
func createOverlay()
{
let p = CGRectMake(self.view!.frame.origin.x, self.view!.frame.origin.y, self.view!.bounds.size.width, self.view!.bounds.size.height)
overlayView = UIView(frame: p)
overlayView.backgroundColor = UIColor.grayColor()
overlayView.alpha = 0.0
self.view!.addSubview(overlayView)
}
func createAlert()
{
let alertWidth: CGFloat = UIScreen.mainScreen().bounds.width
let alertHeight: CGFloat = UIScreen.mainScreen().bounds.height
let alertViewFrame: CGRect = CGRectMake(0,0, alertWidth, alertHeight)
alertView = UIView(frame: alertViewFrame)
alertView.center = self.view.center
alertView.backgroundColor = UIColor.clearColor()
alertView.alpha = 0.0
let button = UIButton(type: UIButtonType.System)
button.setTitle("Cancel", forState: UIControlState.Normal)
button.setTitleColor(UIColor.redColor(), forState: UIControlState.Normal)
button.backgroundColor = UIColor.clearColor()
button.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height)
button.addTarget(self, action: "dismissAlert", forControlEvents: UIControlEvents.TouchUpInside)
button.center.x = alertView.center.x
let button1 = UIButton(type: UIButtonType.RoundedRect)
button1.setTitle("Send", forState: UIControlState.Normal)
button1.backgroundColor = UIColor.greenColor()
button1.frame = CGRectMake(100, 100, 80.0, 80.0)
button1.addTarget(self, action: "Button1", forControlEvents: UIControlEvents.TouchUpInside)
button1.center.x = alertView.center.x
let button2 = UIButton(type: UIButtonType.RoundedRect)
button2.setTitle("Send", forState: UIControlState.Normal)
button2.backgroundColor = UIColor.greenColor()
button2.frame = CGRectMake(100, 200, 80.0, 80.0)
button2.addTarget(self, action: "Button2", forControlEvents: UIControlEvents.TouchUpInside)
button2.center.x = alertView.center.x
let button3 = UIButton(type: UIButtonType.RoundedRect)
button3.setTitle("Send", forState: UIControlState.Normal)
button3.backgroundColor = UIColor.greenColor()
button3.frame = CGRectMake(100, 300, 80.0, 80.0)
button3.addTarget(self, action: "Button3", forControlEvents: UIControlEvents.TouchUpInside)
button3.center.x = alertView.center.x
alertView.addSubview(button)
alertView.addSubview(button1)
alertView.addSubview(button2)
alertView.addSubview(button3)
self.view!.addSubview(alertView)
}
func showAlert()
{
if (alertView == nil)
{
createAlert()
}
UIView.animateWithDuration(0.3)
{
self.overlayView.alpha = 0.5
self.alertView.alpha = 1.0
}
}
func dismissAlert()
{
UIView.animateWithDuration(0.15, animations:
{
self.overlayView.alpha = 0.0
self.alertView.alpha = 0.0
}, completion:
{
(value: Bool) in
self.alertView.removeFromSuperview()
self.alertView = nil
})
}
func Button1()
{
print("button1")
}
func Button2()
{
print("button2")
}
func Button3()
{
print("button3")
}
}
now in Home.swift view controller, I just do the following
import UIKit
class Home: UIViewController
{
override func viewDidLoad()
{
self.PopUpInit()
super.viewDidLoad()
}
var overlayView: UIView!
var alertView: UIView!
var animator: UIDynamicAnimator!
}
the question is :
how do I convert this extension into a class that I can use where ever I need, not only from Home.swift, also I want to avoid the need to define these into all classes that use this pop up
var overlayView: UIView!
var alertView: UIView!
var animator: UIDynamicAnimator!
can you guide me to the best way to do this?
thanks.
The trick to making this more generic is to make it more protocol-oriented. With protocols you can leverage default implementations where you define what the conforming type will need to be like and use the extension to implement methods.
Stuff:
import UIKit
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
PopUpAble:
This will be your main protocol. Anything that has a view variable can be made PopUpAble.
#objc protocol PopUpAble : class {
var view : UIView! { get set }
func button1()
func button2()
func button3()
func alertWillDismiss() -> Bool
func alertDidDismiss()
func dismissAlert()
}
Types for identification:
These are protocols and conforming classes to be able to distinguish the alertView and overlayView without needing to assign them to attributes. Less boilerplate code -> Better
protocol AlertType : class {}
protocol OverlayType : class {}
class AlertView : UIView, AlertType {}
class OverlayView : UIView, OverlayType {}
The Implementation:
This is the same collection of functions as you had, only modified to use what is available in the PopUpAble protocol.
create... functions now search for existing matching types or create
no more setup function, was not needed
optional animation
set order of views before/after show/hide
This all makes it more versatile and less needs to implemented in the VC.
extension PopUpAble {
func createOverlay() -> UIView {
if let overlay = (view.subviews.filter { $0 is OverlayType }).first {
return overlay
}
let p = CGRectMake(self.view!.frame.origin.x, self.view!.frame.origin.y, self.view!.bounds.size.width, self.view!.bounds.size.height)
let overlayView = OverlayView(frame: p)
overlayView.backgroundColor = UIColor.grayColor()
overlayView.alpha = 0.0
view!.insertSubview(overlayView, atIndex: 0)
return overlayView
}
func createAlert() -> UIView {
if let alert = (view.subviews.filter { $0 is AlertType }).first {
return alert
}
let alertWidth: CGFloat = UIScreen.mainScreen().bounds.width
let alertHeight: CGFloat = UIScreen.mainScreen().bounds.height
let alertViewFrame: CGRect = CGRectMake(0,0, alertWidth, alertHeight)
let alertView = AlertView(frame: alertViewFrame)
alertView.center = self.view.center
alertView.backgroundColor = UIColor.clearColor()
alertView.alpha = 0.0
let button = UIButton(type: UIButtonType.System)
button.setTitle("Cancel", forState: UIControlState.Normal)
button.setTitleColor(UIColor.redColor(), forState: UIControlState.Normal)
button.backgroundColor = UIColor.clearColor()
button.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height)
button.addTarget(self, action: #selector(Self.dismissAlert), forControlEvents: UIControlEvents.TouchUpInside)
button.center.x = alertView.center.x
let button1 = UIButton(type: UIButtonType.RoundedRect)
button1.setTitle("Send", forState: UIControlState.Normal)
button1.backgroundColor = UIColor.greenColor()
button1.frame = CGRectMake(100, 100, 80.0, 80.0)
button1.addTarget(self, action: #selector(Self.button1), forControlEvents: UIControlEvents.TouchUpInside)
button1.center.x = alertView.center.x
let button2 = UIButton(type: UIButtonType.RoundedRect)
button2.setTitle("Send", forState: UIControlState.Normal)
button2.backgroundColor = UIColor.greenColor()
button2.frame = CGRectMake(100, 200, 80.0, 80.0)
button2.addTarget(self, action: #selector(Self.button2), forControlEvents: UIControlEvents.TouchUpInside)
button2.center.x = alertView.center.x
let button3 = UIButton(type: UIButtonType.RoundedRect)
button3.setTitle("Send", forState: UIControlState.Normal)
button3.backgroundColor = UIColor.greenColor()
button3.frame = CGRectMake(100, 300, 80.0, 80.0)
button3.addTarget(self, action: #selector(Self.button3), forControlEvents: UIControlEvents.TouchUpInside)
button3.center.x = alertView.center.x
alertView.addSubview(button)
alertView.addSubview(button1)
alertView.addSubview(button2)
alertView.addSubview(button3)
view!.insertSubview(alertView, atIndex: 1)
return alertView
}
func showAlert(animated animated:Bool = true) {
func show(alert:UIView,overlay:UIView) {
if animated {
UIView.animateWithDuration(0.3) {
overlay.alpha = 0.5
alert.alpha = 1.0
}
} else {
overlay.alpha = 0.5
alert.alpha = 1.0
}
}
let overlay = createOverlay()
let alert = createAlert()
view.bringSubviewToFront(overlay)
view.bringSubviewToFront(alert)
show(alert, overlay: overlay)
}
func hideAlert(animated animated:Bool = true) {
guard alertWillDismiss() else {
return
}
func hide(alert:UIView,overlay:UIView) {
if animated {
UIView.animateWithDuration(0.15, animations: {
overlay.alpha = 0.0
alert.alpha = 0.0
}) {
if $0 {
self.view.sendSubviewToBack(alert)
self.view.sendSubviewToBack(overlay)
self.alertDidDismiss()
}
}
} else {
overlay.alpha = 0.0
alert.alpha = 0.0
view.sendSubviewToBack(alert)
view.sendSubviewToBack(overlay)
alertDidDismiss()
}
}
let overlay = createOverlay()
let alert = createAlert()
hide(alert, overlay: overlay)
}
}
The Works:
alertWillDismiss and alertDidDismiss are not needed, but they follow nicely with the standard UIKit flow and give you more control.
class VC : UIViewController, PopUpAble {
func button1() {
print("button1")
}
func button2() {
print("button2")
}
func button3() {
print("button3")
}
func alertWillDismiss() -> Bool {
return true
}
func alertDidDismiss() {
print("dismissed")
}
func dismissAlert() { // this bridges between objc en swift
self.hideAlert()
}
}
let vc = VC()
vc.view.backgroundColor = UIColor.greenColor()
vc.showAlert(animated: true)
XCPlaygroundPage.currentPage.liveView = vc.view
Another way to go is to get the current visible viewcontroller and present the alert on it. Instead of defining actions in a UIViewController subclass they are implemented in a PopUp object. When needed a PopUp is created from wherever and it will be displayed on the current VC.
Stuff:
import UIKit
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
Current ViewController:
Can't take the credit for this code, is from somewhere on SO. (if someone has a link?)
public extension UIWindow {
var visibleViewController: UIViewController? {
return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
}
private static func getVisibleViewControllerFrom(vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
}
Same as the other answer:
protocol AlertType : class {}
protocol OverlayType : class {}
class AlertView : UIView, AlertType {}
class OverlayView : UIView, OverlayType {}
The PopUp Object:
Subclassing can be used to override the actions.
class PopUp : NSObject {
func button1Tapped() {
print("button1")
}
func button2Tapped() {
print("button2")
}
func button3Tapped() {
print("button3")
}
func alertWillDismiss() -> Bool {
return true
}
func alertDidDismiss() {
print("dismissed")
}
This is now implemented in the PopUp
The view of the current VC is grabbed and used to display the alert.
func createOverlay() -> UIView? {
guard let window = UIApplication.sharedApplication().windows.first, view = window.visibleViewController?.view else {
return nil
}
if let overlay = (view.subviews.filter { $0 is OverlayType }).first {
return overlay
}
let p = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.bounds.size.width, view.bounds.size.height)
let overlayView = OverlayView(frame: p)
overlayView.backgroundColor = UIColor.grayColor()
overlayView.alpha = 0.0
view.insertSubview(overlayView, atIndex: 0)
return overlayView
}
func createAlert() -> UIView? {
guard let window = UIApplication.sharedApplication().windows.first, view = window.visibleViewController?.view else {
return nil
}
if let alert = (view.subviews.filter { $0 is AlertType }).first {
return alert
}
let alertWidth: CGFloat = UIScreen.mainScreen().bounds.width
let alertHeight: CGFloat = UIScreen.mainScreen().bounds.height
let alertViewFrame: CGRect = CGRectMake(0,0, alertWidth, alertHeight)
let alertView = AlertView(frame: alertViewFrame)
alertView.center = view.center
alertView.backgroundColor = UIColor.clearColor()
alertView.alpha = 0.0
let button = UIButton(type: UIButtonType.System)
button.setTitle("Cancel", forState: UIControlState.Normal)
button.setTitleColor(UIColor.redColor(), forState: UIControlState.Normal)
button.backgroundColor = UIColor.clearColor()
button.frame = CGRectMake(0, 0, view.frame.width, view.frame.height)
button.addTarget(self, action: #selector(dismissAlert), forControlEvents: UIControlEvents.TouchUpInside)
button.center.x = alertView.center.x
let button1 = UIButton(type: UIButtonType.RoundedRect)
button1.setTitle("Send", forState: UIControlState.Normal)
button1.backgroundColor = UIColor.greenColor()
button1.frame = CGRectMake(100, 100, 80.0, 80.0)
button1.addTarget(self, action: #selector(button1Tapped), forControlEvents: UIControlEvents.TouchUpInside)
button1.center.x = alertView.center.x
let button2 = UIButton(type: UIButtonType.RoundedRect)
button2.setTitle("Send", forState: UIControlState.Normal)
button2.backgroundColor = UIColor.greenColor()
button2.frame = CGRectMake(100, 200, 80.0, 80.0)
button2.addTarget(self, action: #selector(button2Tapped), forControlEvents: UIControlEvents.TouchUpInside)
button2.center.x = alertView.center.x
let button3 = UIButton(type: UIButtonType.RoundedRect)
button3.setTitle("Send", forState: UIControlState.Normal)
button3.backgroundColor = UIColor.greenColor()
button3.frame = CGRectMake(100, 300, 80.0, 80.0)
button3.addTarget(self, action: #selector(button3Tapped), forControlEvents: UIControlEvents.TouchUpInside)
button3.center.x = alertView.center.x
alertView.addSubview(button)
alertView.addSubview(button1)
alertView.addSubview(button2)
alertView.addSubview(button3)
view.insertSubview(alertView, atIndex: 1)
return alertView
}
func showAlert(animated animated:Bool = true) {
guard let window = UIApplication.sharedApplication().windows.first, view = window.visibleViewController?.view else {
return
}
guard let overlay = createOverlay(), alert = createAlert() else {
return
}
func show(alert:UIView,overlay:UIView) {
if animated {
UIView.animateWithDuration(0.3) {
overlay.alpha = 0.5
alert.alpha = 1.0
}
} else {
overlay.alpha = 0.5
alert.alpha = 1.0
}
}
view.bringSubviewToFront(overlay)
view.bringSubviewToFront(alert)
show(alert, overlay: overlay)
}
func dismissAlert(animated animated:Bool = true) {
guard let window = UIApplication.sharedApplication().windows.first, view = window.visibleViewController?.view else {
return
}
guard let overlay = createOverlay(), alert = createAlert() else {
return
}
func hide(alert:UIView,overlay:UIView) {
if animated {
UIView.animateWithDuration(0.15, animations: {
overlay.alpha = 0.0
alert.alpha = 0.0
}) {
if $0 {
view.sendSubviewToBack(alert)
view.sendSubviewToBack(overlay)
self.alertDidDismiss()
}
}
} else {
overlay.alpha = 0.0
alert.alpha = 0.0
view.sendSubviewToBack(alert)
view.sendSubviewToBack(overlay)
alertDidDismiss()
}
}
hide(alert, overlay: overlay)
}
}
The Works:
let vc = UIViewController()
let window = UIWindow(frame: UIScreen.mainScreen().bounds)
window.makeKeyAndVisible()
window.rootViewController = vc
vc.view.backgroundColor = UIColor.greenColor()
XCPlaygroundPage.currentPage.liveView = vc.view
let pop = PopUp()
pop.showAlert(animated: true)
Note:
I would remove the alert and overlay from the view after hiding. No need to keep them around.