I have a UITableView, where each row is a custom XIB. One row has 4 UIButtons, that are choices, like Question, Information, etc. When the UIButton is tapped I display an animation in a CALayer. When the table view scrolls, the CALayer is removed, that is the animation is gone.
When the button is tapped, I crate the CALayer and the animation begins. How can I make sure the CALayer doesn't disappear when the table is scrolled or updated?
class ReasonForSupportTableViewCell: UITableViewCell {
let animationView = AnimationView()
var hasButtonBeenTapped = false
var previousButtonTapped: UIButton? = nil
//IBOutlets
#IBOutlet weak var questionButton: UIButton!
#IBOutlet weak var informationButton: UIButton!
#IBOutlet weak var crashButton: UIButton!
#IBOutlet weak var bugButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
//set initial button
questionButton.sendActions(for: .touchUpInside)
}
func createView(sender: UIButton) {
//draw the frame
animationView.frame = CGRect(x: 0, y: 0, width: 75.0, height: 75.0)
//change the view background color
animationView.backgroundColor = UIColor.clear
animationView.isUserInteractionEnabled = false
//add the view.
sender.addSubview(animationView)
}
#IBAction func buttonTapped(_ sender: UIButton) {
if previousButtonTapped == nil {
//you haven't tapped a button yet
createView(sender: sender)
animationView.animateTo()
previousButtonTapped = sender
} else if previousButtonTapped == sender {
animationView.removeAnimation()
previousButtonTapped = nil
} else {
animationView.removeAnimation()
createView(sender: sender)
animationView.animateTo()
previousButtonTapped = sender
}
}
}
Subclass UIButton to add the CALayer on initialisation, rather than when a button is tapped. You can then use the prepareForReuse() function to stop any animation.
Related
I'm working on a crossword puzzle app for swift and I am having trouble getting a UIButton from a subview in the UIButton that is a textfield.
The text field takes up the space where the UIButton title should be, but when clicking on the textfield it doesn't click the UIButton.
The UIButton itself as of now, highlights all the UIButtons of the some column and row.
There a few things I've tried such as graving the superclass of the subview
var myButton: CustomButton = textfield.superclass as? CustomButton
and I also tried using
var myObject : CustomButton? {
return view.compactMap({$0 as? CustomButton }).first
}
In the CustomButton.swift
class CustomButton: UIButton {
var number: Int = 0
var letter: Character?
var textField: UITextField!
var cornerLabel: UILabel!
// this init will intialize the button through programatically
override init(frame: CGRect) {
super.init(frame: frame)
textField = UITextField(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
textField.translatesAutoresizingMaskIntoConstraints = false
textField.center = .zero
textField.textAlignment = .center
textField.text = ""
I have a ButtonStore.swift that stores all the CustomButtons in the array so I can manage them and retrieve certain ones.
And the MainController.swift has all the reference CustomButton. I am using a UITextFieldDelegate from the MainController
class MainController : UIViewController, UITextFieldDelegate{
var buttonStore : ButtonStore!
#IBOutlet var A1 : CustomButton!
#IBAction func buttonIsPress(sender: CustomButton){
let button : CustomButton = sender
let identifier : String = sender.accessibilityIdentifier ?? ""
// Clear all backgroundbox back to white unless the background is black
buttonStore.removeHighlights()
// Highlight the button pressed and its column and row
buttonStore.highlightRowColumn(identifier: identifier)
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool
{
print("something happened")
// TRIED RECEIVING THE CUSTOMBUTTON HERE
return true
}
The expected result is to have had the CustomButton trigger when textfield is pressed or grabbing the CustomButton of the textfield pressed so I use the CustomButton as reference.
You can get the superview by
if let button = textField.superview as? CustomButton {
// Do what you want to here
}
You can grab any superview or any parent view controller generically in a type safe way by walking up the responder chain (because both UIViewController and UIView inherit from UIResponder and implement its next method):
extension UIResponder {
func firstParent<T: UIResponder>(ofType type: T.Type ) -> T? {
return next as? T ?? next.flatMap { $0.firstParent(ofType: type) }
}
}
Use like:
if let button = textField.firstParent(ofType: CustomButton.self) {
//Do button stuff here
}
This method has the advantage that you can find the next parent of a particular type even if it isn't the immediate parent (ie you may have several views between the textfield and the CustomButton and this still works, while calling superview does not).
My View hierarchy looks like this:
ElevethViewController of type UIViewController
Container View
ManagedTableEleventhViewController of type UITableViewController embedded in Container View
ManagedTableEleventhViewController contains 4 static cells containing 1 textField each and one empty static cell.
class ManagedTableEleventhViewController: UITableViewController,UITextFieldDelegate {
var hasText:Bool!
#IBOutlet weak var fullName: UITextField!
#IBOutlet weak var flatNumber: UITextField!
#IBOutlet weak var streetAddress: UITextField!
#IBOutlet weak var phoneNumber: UITextField!
//checkValue takes ELViewController parameter so that segue can be
//performed when button is touched in EleventhViewController
func checkValue(ELViewController:EleventhViewController) {
//loop through the textfields and check if they have text
for case let textField as UITextField in viewController.view.subviews {
//print is not executed meaning loop is not performed
print("some text")
if textField.text == "" {
self.hasText = false
textField.layer.borderColor = UIColor.red.cgColor
} else {
print("true value in for loop")
self.hasText = true
performSegue(withIdentifier: "elevethToTwelveth", sender: ELViewController)
}
}//end of for loop
}
class EleventhViewController: UIViewController {
var nextButtonOutlet:UIButton!
override func viewDidLoad() {
super.viewDidLoad()
//create button programmatically
var button = UIButton(type: UIButtonType.custom) as UIButton
button = UIButton(frame: CGRect(x: 0, y: 637, width: 375, height: 50))
button.titleLabel?.textColor = UIColor.white
button.backgroundColor = UIColor(colorLiteralRed: 117/255, green: 232/255, blue: 0, alpha: 1)
button.setTitle("Next", for: .normal)
button.addTarget(self, action: #selector(EleventhViewController.nextButton), for: .touchUpInside)
self.view.addSubview(button)
self.nextButtonOutlet = button
}
func nextButton(sender: UIButton) {
//create instance of tableView
let managedTable = ManagedTableEleventhViewController()
managedTable.checkValue(viewController: self)
} //end of EleventhViewController class
Well first I can give you an answer that might satisfy you and fix your loop but I would recommend not doing it that way to alter your textfields. I would recommend doing it in cellForRow even though they may be static cells. Depending on your view setup in the cells it would look like this if the textfield is added directly to the cells and not to another view.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Testing")
for cell in tableView.visibleCells{
for sub in cell.contentView.subviews{
if sub is UITextField{
print("Textfield")
}
}
}
}
Just to follow up, if this is for validation you should'nt be only checking "" case, because you allow " ", " " etc. Use isEmpty, it should work better, if you only want to check existence of text.
Also you dont have to extract fields from subviews as you already have properties, i'm not sure if you have any other reason for this logic though.
Edit. Ooops, i just noticed your checking for textfields in a controller which does not have any visible fields, so normally your check never passes.
I think you should'nt even validate textfields for one class in another class, unless its a class handling textfield validation in general.
In EleventhViewController you have no textfields, so nothing to validate.
I have a TextView and a hidden button within a UIView, and I'm trying to detect when the user scrolls down to the bottom of a long list of text and to show the hidden button when they reach the bottom. I saw some old posts on how it was done in Obj-C using scrollViewDidScroll, but not really sure how to do that with swift, or how to do it with a TextView instead of a ScrollView. Any help would be great as I haven't gone very far with this one.
So far this is my attempt at translating the obj-c post to swift, but it hasn't worked for me, in fact I'm not even sure when the function is called:
import UIKit
class MainVC: UIViewController, UIScrollViewDelegate {
#IBOutlet var textView: UIScrollView!
#IBOutlet var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
}
func scrollViewDidScroll(textV: UIScrollView) {
if (textV.contentOffset.y >= textV.contentSize.height - textV.frame.size.height)
{
button.isHidden = false
}
}
}
Thanks for any help in advance :)
UITextView is subclass of UIScrollView and if you look to declaration, you will see, that it is UIScrollViewDelegate by default, so you can remove the UIScrollViewDelegate at the declaration of your controller. Instead, make your controller UITextViewDelegate which allows it to call scrollViewDidScrollMethod.
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var textView: UITextView! {
didSet {
textView.delegate = self
}
}
#IBOutlet weak var button: UIButton! {
didSet {
button.hidden = true
}
}
func scrollViewDidScroll(scrollView: UIScrollView) {
button.hidden = scrollView.contentOffset.y + scrollView.bounds.height < scrollView.contentSize.height
}
}
(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
float bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height
if (bottomEdge >= scrollView.contentSize.height) {
self.yourButtonName.hidden = true
}
}
If the button is in the last of view(self.view) then I think you have to check that your contentOffset point is at the bottom of contentSize. So you could probably do something like:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
float bottomEdge = scrollView.contentOffset.y +scrollView.frame.size.height;
if (bottomEdge >= scrollView.contentSize.height) {
self.yourButtonName.hidden = true
}
}
I am having trouble figuring out why a UIButton will not click. I have a UIScrollView that contains a UIStackView where I am programmatically placing subviews. I am creating the subviews and placing them into the stack view with the following:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let vc0 = self.storyboard?.instantiateViewControllerWithIdentifier("vc0") as UIViewController!
let vc1 = self.storyboard?.instantiateViewControllerWithIdentifier("vc1") as UIViewController!
self.stackView.addArrangedSubview(vc0.view)
self.stackView.addArrangedSubview(vc1.view)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// mulitply by two in order to scale it to the size of each `addArrangedSubview`, subtract the height of the embedded navbar
scrollView.contentSize = CGSize(width: stackView.frame.width * 2, height: stackView.frame.height - 64)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The only other changes I have made are with the constraints:
I should note that vc0 and vc1 are simple UIViews with a UIButton set to show another simple UIView.
Any help would be much appreciated!
EDIT:
I have added a button to vc0 programmatically and the click is working. Why would this button work and one from storyboard not work?
...
let vc0 = self.storyboard?.instantiateViewControllerWithIdentifier("vc0") as UIViewController!
...
let btn=UIButton(type: .System)
btn.frame = CGRectMake(100, 100, 32, 32)
btn.addTarget(self, action: "pressed", forControlEvents: .TouchUpInside)
btn.setTitle("Press Me", forState: .Normal)
vc0.view.addSubview(btw)
...
I have been working on a mainly text-based application with an Iadbanner in the bottom.
However as we all know, Iads aren't always there. So I would like to be able to dynamically update the height of my textView, so when the banner is hidden, the textView takes up the wasted space. And resize it when a banner is loaded.
Here's what I currently have for the Viewcontroller in question
import UIKit
import iAd
class DetailVC: UIViewController, UITextViewDelegate, ADBannerViewDelegate {
//Our label for displaying var "Items/cellName"
#IBOutlet var imageViewOutlet: UIImageView!
//connect in IB connection inspector with your ADBannerView
#IBOutlet var adBannerView: ADBannerView!
//Receiving variable assigned to our mainVC var "items"
var cellName: String = ""
var imageView: UIImageView = UIImageView()
var image = UIImage(named: "handcuffs.png")
var textViewText: String = ""
var textView: UITextView = UITextView(frame: CGRect(x: 5.0, y: 238.0, width: 315.00, height: 283.00))
//height = 332 for full screen 283 for small
override func viewDidLoad() {
super.viewDidLoad()
println("+--------------------+")
println("| Detail view loaded |")
println("+--------------------+")
// Iad stuff
self.adBannerView.delegate = self
self.canDisplayBannerAds = true
self.adBannerView.hidden = true //hide until ad loaded
//Setting up the textView
textView.text = textViewText
textView.editable = false
textView.backgroundColor = UIColor.clearColor()
textView.font = UIFont(name: "Helvetica", size: 15)
//adding textview as subview
self.view.addSubview(textView)
//ImageViewOutlets
imageViewOutlet.image = image
//Assign your string var to your navbar title
self.title = cellName
func bannerViewWillLoadAd(banner: ADBannerView!) {
NSLog("bannerViewWillLoadAd")
}
func bannerViewDidLoadAd(banner: ADBannerView!) {
NSLog("bannerViewDidLoadAd")
//self.textView.removeFromSuperview()
//textView = UITextView(frame: CGRect(x: 0.0, y: 238.0, width: 320.00, height: 283.00))
//self.view.addSubview(textView)
self.adBannerView.hidden = false //now show banner as ad is loaded
}
func bannerViewActionDidFinish(banner: ADBannerView!) {
NSLog("bannerViewActionDidFinish")
//optional resume paused app code
}
func bannerViewActionShouldBegin(banner: ADBannerView!, willLeaveApplication willLeave: Bool) -> Bool {
NSLog("bannerViewActionShouldBegin")
//optional pause app code
return true
}
func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) {
NSLog("didFailToReceiveAdWithError")
}
//... your class implementation code
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The commented out code in the function "bannerViewDidLoadAd" is what I thought would have fixed my issue. Sadly that function seems to never run? I'm not very familiar with Iads so hopefully someone out there can give me a hint as to how to change the height of a textView when an ad loads.
CanDisplayBannerAds :
Set this to enable automatic management of banner ad display with the view controller. It's important to note that this will modify the view hierarchy of the view controller by inserting a new container view above the view controller's view. The impact is that the view controller's view property will no longer return the originally provided view, it will return the new container. To access the original view, use the originalContentView property.
So, remove this line :
self.canDisplayBannerAds = true
Everything should work.