UIButton works normally, but not in popoverPresentationController - ios

I have a parent container UIView that has three subviews: 1 UITextView, and 2 buttons (the buttons are on top of the UITextView, but contained as subviews of the container UIView). When I present the parent UIView on screen normally, simply inside of a UIViewController, the buttons trigger their associated action methods perfectly and function correctly. However, when I present the UIView inside a view controller that becomes a popover, the view hierarchy shows up properly, but the buttons don't trigger their associated action methods and nothing happens. Is there something about UIPopoverPresentationControllers and buttons that I don't understand?
Adding the buttons to the parent UIView
func layoutButtons(in parent: UIView) {
let speechButton = UIButton(type: .custom)
speechButton.frame = CGRect(x: parent.frame.width - 35, y: parent.frame.height - 35, width: 30, height: 30)
speechButton.setImage(UIImage(named: "sound_icon"), for: .normal)
speechButton.addTarget(self, action: #selector(Textual.textToSpeech), for: UIControlEvents.touchUpInside)
parent.addSubview(speechButton)
let fontSizeButton = UIButton(type: .custom)
fontSizeButton.frame = CGRect(x: textView.frame.width - 75, y: textView.frame.height - 35, width: 30, height: 30)
fontSizeButton.setImage(UIImage(named: "font_size_icon"), for: .normal)
fontSizeButton.addTarget(self, action: #selector(Textual.toggleFontSize), for: UIControlEvents.touchUpInside)
parent.addSubview(fontSizeButton)
// wrap text around the bottom buttons
let excludeSpeechButton = UIBezierPath(rect: speechButton.frame)
let excludeFontSizeButton = UIBezierPath(rect: fontSizeButton.frame)
self.textView.textContainer.exclusionPaths = [excludeSpeechButton, excludeFontSizeButton]
}
#objc func textToSpeech() {
if synth.isPaused {
synth.continueSpeaking()
} else if synth.isSpeaking {
synth.pauseSpeaking(at: .immediate)
} else {
let speechUtterance = AVSpeechUtterance(string: attributedText.string)
speechUtterance.rate = 0.5
synth.speak(speechUtterance)
}
}
#objc func toggleFontSize() {
if self.smallFontSizeMode == true {
self.smallFontSizeMode = false
} else {
self.smallFontSizeMode = true
}
// for each character, multiply the current font size by fontSizeModifier
// http://stackoverflow.com/questions/19386849/looping-through-nsattributedstring-attributes-to-increase-font-size
self.attributedText.beginEditing()
self.attributedText.enumerateAttribute(NSFontAttributeName, in: NSMakeRange(0, self.attributedText.length), options: NSAttributedString.EnumerationOptions.reverse, using: { (value, range, stop) in
if let oldFont = value as? UIFont {
var newFont: UIFont?
if self.smallFontSizeMode == true { // was big and now toggling to small
newFont = oldFont.withSize(oldFont.pointSize / 2)
} else { // was small and now toggling to big
newFont = oldFont.withSize(oldFont.pointSize * 2)
}
attributedText.removeAttribute(NSFontAttributeName, range: range)
attributedText.addAttribute(NSFontAttributeName, value: newFont!, range: range)
}
})
self.attributedText.endEditing()
self.textView.attributedText = self.attributedText
}
Presenting the UIViewController
func present(viewController: UIViewController, at location: CGRect) {
viewController.modalPresentationStyle = .popover
parentViewController.present(viewController, animated: true, completion: nil)
let popController = viewController.popoverPresentationController
popController?.permittedArrowDirections = .any
popController?.sourceView = parentView
popController?.sourceRect = location
}
UPDATE - I've added popController?.delegate = viewController as the last line of func present(viewController:at:) and below I've added extension UIViewController: UIPopoverPresentationControllerDelegate { }

To answer your question, you will need to tell your presenting view controller who to delegate, so add this below and let me know if it works:
popController?.delegate = viewController

Related

How to build a context menu like Facebook / Slack on iOS?

I was just looking at Context menu of Facebook and or slack and wanted to create something similar in my App.
I have tried two methods.
First method. Having a in View Table View and sliding it from bottom to create as if it is animated on to the view. But the problem with this is that The navigation controller and Tab bar controller are not hidden and a white patch is shown over the Black (Alpha 30 %).
The second method I tried was showing a new View controller over the current view controller and presenting as a Modal presentation.
let vc = CustomActionTableViewController(nibName: "CustomActionTableViewController", bundle: nil)
vc.modalPresentationStyle = .overFullScreen
self.present(vc, animated: false, completion: nil)
This works okay but the method is too slow as I have to work with lot of Notifications (To send selected index to my main View and then perform action). It is painfully slow.
Could anyone help me with how I can improve the implementation so that I can get the Action sheet similar to Facebook which is smooth and very very fluid
Check this example : Bottom pop Up
Currently I am using this in my app and it's work fine.
Since you mentioned Slack, they actually have open sourced their bottom sheet implementation, PanModal.
Using UIPresentationController and UIPanGestureRecognizer
1- create BottomMenu presentation Controller which will handle the height of your View Controller and blur
class BottomMenuPresentationController: UIPresentationController {
// MARK: - Properties
var blurEffectView: UIVisualEffectView?
var tapGestureRecognizer = UITapGestureRecognizer()
private var topHeightRatio: Float
private var bottomHeightRatio: Float
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, topHeightRatio: Float, bottomHeightRatio: Float) {
let blurEffect = UIBlurEffect(style: .systemThickMaterialDark)
blurEffectView = UIVisualEffectView(effect: blurEffect)
self.topHeightRatio = topHeightRatio
self.bottomHeightRatio = bottomHeightRatio
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
blurEffectView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissController))
self.blurEffectView?.isUserInteractionEnabled = true
self.blurEffectView?.addGestureRecognizer(tapGestureRecognizer)
}
override var frameOfPresentedViewInContainerView: CGRect {
CGRect(origin: CGPoint(x: 0, y: self.containerView!.frame.height * CGFloat(topHeightRatio)),
size: CGSize(width: self.containerView!.frame.width, height: self.containerView!.frame.height * CGFloat(bottomHeightRatio)))
}
override func presentationTransitionWillBegin() {
self.blurEffectView?.alpha = 0
if let blurEffectView = blurEffectView {
self.containerView?.addSubview(blurEffectView)
}
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (_) in
self.blurEffectView?.alpha = 0.66
}, completion: { (_) in })
}
override func dismissalTransitionWillBegin() {
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (_) in
self.blurEffectView?.alpha = 0
}, completion: { (_) in
self.blurEffectView?.removeFromSuperview()
})
}
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedView!.roundCorners([.topLeft, .topRight], radius: 14)
}
override func containerViewDidLayoutSubviews() {
super.containerViewDidLayoutSubviews()
presentedView?.frame = frameOfPresentedViewInContainerView
blurEffectView?.frame = containerView!.bounds
}
#objc func dismissController() {
self.presentedViewController.dismiss(animated: true, completion: nil)
}
}
2- create Your ViewController
class BottomMenuVC: UIViewController {
// MARK: - Instances
var hasSetPointOrigin = false
var pointOrigin: CGPoint?
// MARK: - Properties
let topDarkLine: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexString: "#E1E1E1")
view.layer.cornerRadius = 2
return view
}()
let cancelButn: UIButton = {
let button = UIButton(type: .custom)
button.setAttributedTitle(NSAttributedString(string: "Cancel", attributes: [NSAttributedString.Key.font: UIFont.LatoMedium(size: 17),
NSAttributedString.Key.foregroundColor: UIColor(hexString: "#515151")
]), for: .normal)
button.backgroundColor = UIColor(hexString: "#F1F3F4")
button.layer.cornerRadius = 5.0
button.addTarget(self, action: #selector(cancelButnPressed), for: .touchUpInside)
return button
}()
// MARK: - viewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.isUserInteractionEnabled = true
setupMenuView()
}
override func viewDidLayoutSubviews() {
if !hasSetPointOrigin {
hasSetPointOrigin = true
pointOrigin = self.view.frame.origin
}
}
// MARK: - SetupView
func setupMenuView() {
self.view.addSubview(topDarkLine)
self.view.addSubview(cancelButn)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognizerAction(_:)))
view.addGestureRecognizer(panGesture)
topDarkLine.constrainHeight(constant: 4)
topDarkLine.constrainWidth(constant: view.frame.size.width * 0.10)
topDarkLine.centerXInSuperview()
topDarkLine.anchor(top: view.topAnchor, leading: nil, bottom: nil, trailing: nil, padding: .init(top: 8, left: 0, bottom: 0, right: 0))
cancelButn.anchor(top:view.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor,
padding: .init(top: 16, left: 16, bottom: 0, right: 16))
cancelButn.constrainHeight(constant: 44)
}
// MARK: - Actions
#objc func panGestureRecognizerAction(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: view)
// Not allowing the user to drag the view upward
guard translation.y >= 0 else { return }
// setting x as 0 because we don't want users to move the frame side ways!! Only want straight up or down in the y-axis
view.frame.origin = CGPoint(x: 0, y: self.pointOrigin!.y + translation.y)
if sender.state == .ended {
let dragVelocity = sender.velocity(in: view)
if dragVelocity.y >= 1300 {
// Velocity fast enough to dismiss the uiview
self.dismiss(animated: true, completion: nil)
} else {
// If the dragging isn’t too fast, resetting the view back to it’s original point
UIView.animate(withDuration: 0.3) {
self.view.frame.origin = self.pointOrigin ?? CGPoint(x: 0, y: 400)
}
}
}
}
#objc func cancelButnPressed() {
dismiss(animated: true, completion: nil)
}
}
3- make the viewController that contain the button that will present your menu conforms to UIViewControllerTransitioningDelegate
extension viewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
BottomMenuPresentationController(presentedViewController: presented, presenting: presenting, topHeightRatio: 0.6, bottomHeightRatio: 0.4)
}
}
4- set the transitioning delegate to self and present your custom presentation Controller
func showBottomMenu() {
let menu = BottomMenuVC()
menu.coordinator = self
menu.modalPresentationStyle = .custom
menu.transitioningDelegate = self
present(menu, animated: true, completion: nil)
}
check this PanGesture Slidable View article

Swift: Changing (translate) a UIView position through a pan gesture in its superview window

Introduction
Context:
In my main ViewController I have a scrollView with a few objects inside (which are UIViews). When one of the UIViews are tapped/selected I animate forward a UITextView in a UIView to go with the selected object. (only one UIView can appear at a time)
This UIView that appears on object selection is separated into a separate class called AdjunctiveTextView.
Issue/goal:
(the example code provided below will clear make this clear, I've also commented where the issue lies in the code)
When an object has been tapped and has an adjacent UIView with a text I want to have that adjacent UIView to follow with the scrollView.
I'm using a UIPanGestureRecognizer to attempt to do this. But I can't figure out how to make it work when the user drags in the scrollview. It only work if the user drags on the actual adjunctiveTextView.
Everything works as expected except that the adjunctiveTextView does not change its position during the panGesture.
I would like (if possible) to have the AdjunctiveTextView as a separate class. My ViewController file is getting rather big.
Question:
Why doesn't the UIPanGestureRecognizer work as expected? What is needed in order for it to translate the backView correctly?
Code
My attempt: (as shown below)
My attempt simply makes the backView itself "dragable" around through the panGesture. Nothing happens to it when I scroll the scrollView.
(I have only included relevant portions of my code)
class ViewController: UIViewController {
let adjunctiveTextView = AdjunctiveTextView()
// this is a delegate method which gets called when an object is tapped in the scrollView
func scrollViewObjectIsTapped(_ objectScrollView: ObjectScrollView, object: AvailableObject) {
** adjunctiveTextView.scrollView = scrollView // **Edited! (scrollView is the name of the scrollView in this class too)
adjunctiveTextView.showView(passInObject: AvailableObject)
}
}
class AdjunctiveTextView: NSObject {
lazy var backView: UIView = {
//backView setup
}
lazy var textView: UITextView = {
//textView setup
}
//additional init and setup
** weak var scrollView : UIScrollView! // **Edited!
func showView(passInObject: AvailableObject) {
if let window = UIApplication.shared.keyWindow {
// the issue must either be here in the PanGesture setup
let panG = UIPanGestureRecognizer(target: self, action: #selector(translateView(sender:)))
panG.cancelsTouchesInView = false
// window.addGestureRecognizer(panG)
** scrollView.addGestureRecognizer(panG) // **Edited!
window.addSubview(backView)
textView.text = passInObject.information
backView.frame = CGRect(x: passInObject.frame.minX, y: passInObject.minY, width: window.frame.width - passInObject.maxX - 6, height: textView.bounds.height + 5)
backView.alpha = 0
//it animates a change of the backViews x position and alpha.
UIView.animate(withDuration: 0.42, delay: 0, options: .curveEaseInOut, animations: {
self.backView.alpha = 1
self.backView.frame = CGRect(x: passInObject.frame.minX + passInObject.frame.width, y: passInObject.minY, width: window.frame.width - passInObject.maxX - 6, height: textView.bounds.height + 5)
}, completion: nil)
}
}
// or the issue is here in the handle function for the PanGesture.
#objc private func translateView(sender: UIPanGestureRecognizer) {
if let window = UIApplication.shared.keyWindow {
let translation = sender.translation(in: window) //Have tried setting this to scrollView also
switch sender.state {
case .began, .changed:
backView.center = CGPoint(x: backView.center.x, y: backView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: window) //Have tried setting this to sccrollView also
break
case .ended:
break
default:
break
}
}
}
}
Thanks for reading my question.
I just add a weak reference to your scrollView and then add the pan gesture to scrollView. It works as you want. You may consider add another pan gesture to the back view if you want your original behavior.
class AdjunctiveTextView: NSObject {
lazy var backView: UIView = {
//backView setup
return UIView.init()
}()
lazy var textView: UITextView = {
//textView setup
return UITextView.init(frame: CGRect.init(x: 0, y: 0, width: 300, height: 100))
}()
weak var scrollView: UIScrollView!
//additional init and setup
func showView(passInObject: AvailableObject) {
if let window = UIApplication.shared.keyWindow {
// the issue must either be here in the PanGesture setup
let panG = UIPanGestureRecognizer(target: self, action: #selector(translateView(sender:)))
panG.cancelsTouchesInView = false
// passInObject.addGestureRecognizer(panG)
scrollView.addGestureRecognizer(panG)
window.addSubview(backView)
textView.text = passInObject.information
textView.backgroundColor = UIColor.blue
backView.addSubview(textView)
backView.frame = CGRect(x: passInObject.frame.minX, y: passInObject.frame.minY, width: window.frame.width - passInObject.frame.maxX - 6, height: textView.bounds.height + 5)
backView.alpha = 0
//it animates a change of the backViews x position and alpha.
UIView.animate(withDuration: 0.42, delay: 0, options: .curveEaseInOut, animations: {
self.backView.alpha = 1
self.backView.frame = CGRect(x: passInObject.frame.minX + passInObject.frame.width , y: passInObject.frame.minY , width: window.frame.width - passInObject.frame.maxX - 6, height: self.textView.bounds.height + 5)
self.backView.backgroundColor = UIColor.red
}, completion: nil)
}
}
// or the issue is here in the handle function for the PanGesture.
#objc private func translateView(sender: UIPanGestureRecognizer) {
if let window = UIApplication.shared.keyWindow {
let translation = sender.translation(in: window)
switch sender.state {
case .began, .changed:
backView.center = CGPoint(x: backView.center.x, y: backView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: window)
break
case .ended:
break
default:
break
}
}
}
}
class ObjectScrollView: UIScrollView{
}
class AvailableObject: UIView{
var information: String!
}
class MySCNViewController: UIViewController {
#IBOutlet weak var oScrollView: ObjectScrollView!
// this is a delegate method which gets called when an object is tapped in the scrollView
func scrollViewObjectIsTapped(_ objectScrollView: ObjectScrollView, object: AvailableObject) {
adjunctiveTextView.showView(passInObject: object)
}
let adjunctiveTextView = AdjunctiveTextView()
let ao = AvailableObject.init(frame: CGRect.init(x: 0, y: 0, width: 200, height: 200))
override func viewDidLoad() {
super.viewDidLoad()
ao.information = "test"
adjunctiveTextView.scrollView = oScrollView
ao.backgroundColor = UIColor.yellow
}
#IBAction func tap(_ sender: Any?){
scrollViewObjectIsTapped(oScrollView, object: ao)}
}

Select a specify subview swift

how can i select a subview and interact with it using swift and n subview. For now i have only 3 subviews and select 3 image with for.
But for example how can I remove the subview with tag 2 after i create it?
func addSubView() {
for index in 1...3 {
let image: UIImage = UIImage(named: String(index))!
imageView = UIImageView(image: image)
imageView.tag = index
imageView.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.removeSubview))
imageView.addGestureRecognizer(tapGesture)
imageView.frame = CGRect(x: randomNumber(range: 60...300), y: randomNumber(range: 60...400), width: 50, height: 50)
print(imageView)
self.backgroundImageView.addSubview(imageView)
}
}
func removeSubview() {
}
You could write a function removeSubviewWithTag(_:) that would take a tag number as a parameter:
func removeSubviewWithTag(_ tag: Int) {
if let viewWithTag2 = backgroundImageView.viewWithTag(tag) {
viewWithTag2.removeFromSuperview()
}
}
And then call it as desired:
removeSubviewWithTag(2)
If you want to know if the function was able to find and remove a subview, you could make it return a discardable Bool result:
#discardableResult func removeSubviewWithTag(_ tag: Int) {
if let viewWithTag2 = backgroundImageView.viewWithTag(tag) {
viewWithTag2.removeFromSuperview()
return true
} else {
return false
}
}
And call it as follows:
if removeSubviewWithTag(2) {
print("Removed view")
} else {
print("unable to remove view")
}
Maybe add them to an array? Using tags is rarely the best solution.
var imageViews = [UIImageView]()
func addSubView() {
for index in 1...3 {
let image: UIImage = UIImage(named: String(index))!
imageView = UIImageView(image: image)
imageView.tag = index
imageView.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.removeSubview))
imageView.addGestureRecognizer(tapGesture)
imageView.frame = CGRect(x: randomNumber(range: 60...300), y: randomNumber(range: 60...400), width: 50, height: 50)
print(imageView)
self.backgroundImageView.addSubview(imageView)
imageViews.append(imageView)
}
}
func removeSubview(at index: Int) {
imageViews[index].removeFromSuperview()
}

After a graph from "Charts by Daniel Cohen Gindi" graphs itself, a UIButton on the screen is no longer selectable

I am using the open-sourced Charts by Daniel Cohen Gindi: https://github.com/danielgindi/Charts
I have a pie chart on my screen which graphs itself like this:
//set pie chart data
graphView.data = PieChartData(dataSets: iPieChartDataSet)
Additionally I have a button on the screen which displays a popover similarly to the accepted answer of this question.
"What I Want"
When the user presses the UIButton on the screen, I want a popover to display.
"What works"
When I press the UIButton, the popover is created and presented as expected. Additionally I can create the popover, close it, and recreate it.
"What doesn't work"
If the graph is present on the screen, then the popover does not appear. I tried to using a print statement within the UIButton and it looks like the IBAction is never fired.
Other information
Other UI actions work. I can tab to other tabs and I can swivel the graph around. It just seems that the UIButton is somehow getting turned off if the graph on the screen is present.
Relevant code:
How the popover is being created:
#IBAction func informationButtonTapped(_ sender: UIButton) {
var popoverContent = (self.storyboard?.instantiateViewController(withIdentifier: segueIdentifiers.informationPopover))! as UIViewController
var nav = UINavigationController(rootViewController: popoverContent)
nav.modalPresentationStyle = UIModalPresentationStyle.popover
var popover = nav.popoverPresentationController
popoverContent.preferredContentSize = CGSize(width: 500,height: 600)
popover?.delegate = self
popover?.sourceView = self.view
popover?.sourceRect = CGRect(x: 0, y: 0, width: 100, height: 100)
self.present(nav, animated: true, completion: nil)
}
How the graph is being created (shortened as much as possible):
private func setChart(dataPoints: [String?], values: [Double]) {
var dataEntries: [PieChartDataEntry] = []
for i in 0..<dataPoints.count {
let dataEntry = PieChartDataEntry(value: values[i], label: dataPoints[i])
dataEntries.append(dataEntry)
}
let pieChartDataSet = PieChartDataSet(values: dataEntries, label: "")
var iPieChartDataSet: [IChartDataSet] = []
iPieChartDataSet.append(pieChartDataSet)
graphView.animate(xAxisDuration: 1.0, yAxisDuration: 1.0)
graphView.layer.borderWidth = 0.1
//set pie chart data
graphView.data = PieChartData(dataSets: iPieChartDataSet)
}
For my app I created the buttons programmatically, and I've had no issues so far. Heres my button code, see if it works for you.
var updateButton : UIButton = UIButton(type: .custom)
override func viewDidLoad() {
super.viewDidLoad()
setChart()
let buttonHeight = 60.0
let buttonWidth = 280.0
let xspacing = 45.0
let fontSize:CGFloat = 30
let buttonColor = UIColor.black
let textColor = UIColor.blue
updateButton.frame = CGRect(x: xspacing, y: 400, width: buttonWidth, height: buttonHeight)
updateButton.backgroundColor = buttonColor
updateButton.clipsToBounds = true
updateButton.setTitle("Update", for: .normal)
updateButton.titleLabel!.font = UIFont(name: "ScienceFair", size: fontSize)
updateButton.titleLabel!.textColor = UIColor.black
updateButton.addTarget(self, action: #selector(updateFunc), for: .touchUpInside)
view.addSubview(updateButton)
}
func updateFunc() {
UIView.animate(withDuration: 0.1, animations: { self.updateButton.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) }, completion: { (finish: Bool) in UIView.animate(withDuration: 0.1, animations: { self.updateButton.transform = CGAffineTransform.identity }) })
//Present Popover
}
Ignore stackoverflow formatting for the commented #selector. Its not a comment.

Darkened overlay while user is typing

When the user taps on a text field, I wanted to darken the rest of the screen (everything below the text box, above the keyboard) to make it clear what they should be doing. I believe it involved putting a transparent UI view down and adding a gesture recognizer to it, but I'm not quite sure how to do that.
I've got the following code for when the user arrives on the screen. Is this where I would add the new UI View?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
textField.becomeFirstResponder()
}
Thanks!
I created a subclass of UIView for two reasons:
you can add a gesture recogniser to it
you can add a delegate to it
Since the last subview added to a view is on top I first add the dark layer and then call bringSubviewToFront(textview) this will put the dark layer between the textview in question and everything else.
I created a protocol with one function. This function get's called by the gesture recogniser and returns the instance of DarkView to the delegate. The delegate (your ViewController) can then remove it from it's superview.
This you can do without a delegate function, but you also have to call resignFirstResponder() on the textfield.
Don't forget to set up the delegate of the DarkView in your ViewController.
Just a VC with some textfields.
class FirstViewController: UIViewController, DarkViewDelegate, UITextFieldDelegate {
var masterView : UIView!
override func viewDidLoad() {
super.viewDidLoad()
masterView = UIView(frame: self.view.frame)
masterView.backgroundColor = UIColor.whiteColor()
let textField1 = UITextField(frame: CGRect(x: 50, y: 20, width: 300, height: 20))
let textField2 = UITextField(frame: CGRect(x: 50, y: 60, width: 300, height: 20))
let textField3 = UITextField(frame: CGRect(x: 50, y: 100, width: 300, height: 20))
let textField4 = UITextField(frame: CGRect(x: 50, y: 140, width: 300, height: 20))
func styleTextField(field : UITextField) {
field.layer.borderColor = UIColor.blackColor().CGColor
field.layer.borderWidth = 2
field.layer.cornerRadius = field.frame.size.height / 2
field.backgroundColor = UIColor.whiteColor()
field.delegate = self
masterView.addSubview(field)
}
styleTextField(textField1)
styleTextField(textField2)
styleTextField(textField3)
styleTextField(textField4)
self.view.addSubview(masterView)
// Do any additional setup after loading the view, typically from a nib.
}
// delegate function of a textfield
func textFieldDidBeginEditing(textField: UITextField) {
focusUserOn(textField) // darken everything else
}
// delegate function of DarkView undarken everything
func tappedDark(view: DarkView) {
guard let superV = view.superview else {
return
}
if let textField = superV.subviews.last as? UITextField {
textField.resignFirstResponder() // also stop editing
}
view.removeFromSuperview()
}
func focusUserOn(textfield: UITextField) {
guard let superV = textfield.superview else {
return
}
let darkArea = DarkView(frame: superV.bounds)
darkArea.delegate = self
superV.addSubview(darkArea)// add DarkView (everything is dark now)
superV.bringSubviewToFront(textfield) // bring the textview back to the front.
}
}
simple subclass of UIView with a gesture recogniser
class DarkView : UIView, UIGestureRecognizerDelegate {
weak var delegate : DarkViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.4)
let tap = UITapGestureRecognizer(target: self, action: Selector("tapped"))
tap.delegate = self
self.addGestureRecognizer(tap)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("deinit dark")
}
func tapped() {
guard let del = self.delegate else {
return
}
del.tappedDark(self)
}
}
Protocol to pass the DarkView when it is tapped to a VC
protocol DarkViewDelegate : class {
func tappedDark(view:DarkView)
}
After thought
func textFieldDidEndEditing(textField: UITextField) {
guard let superV = view.superview else {
return
}
for subview in superV.subviews {
if subview is DarkView {
subview.removeFromSuperview()
}
}
}
Yes, you have the right idea. Add your UIView (let's call it darkeningView) to your storyboard and set it's background color to 50% opaque black. Position it where you want it and add constraints that position it there.
You can also attach a tap gesture recognizer to the darkeningView in IB and set up it's delegate. (You will probably need to set userInteractionEnabled = true on the darkeningView so that it responds to taps.
Add an IBOutlet to your view. In IB, set it to hidden = true.
In your code, when you activate the text field for editing, also set your darkeningView.hidden = false.

Resources