I currently have a subclass of UIView which contains numerous subviews. I wish to add a UISwipeGesture to the subviews but unfortunately the swipe gesture is not recognized. I've set userInteractionEnabled = true and direction of the swipe gesture but nothing works.
public class CardStackView: UIView{
public var dataSource = [UIImage]()
private var swipeGuesture: UISwipeGestureRecognizer!
override public func layoutSubviews() {
for img in dataSource{
let view = AppView(image: img, frame: self.frame)
self.addSubview(view)
}
animateSubview()
self.userInteractionEnabled = true
}
func animateSubview(){
for (index, sView) in self.subviews.enumerate() {
swipeGuesture = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGuestureDidSwipeRight(_:)))
swipeGuesture.direction = .Right
sView.addGestureRecognizer(swipeGuesture)
sView.userInteractionEnabled = true
let move: CGFloat = CGFloat(-20 + index * 20)
let opacity = Float(1 - 0.2 * CGFloat(index))
sView.shadowOpacity(opacity).shadowOffset(CGSizeMake(20 - CGFloat(index) * 5, 20 - CGFloat(index) * 5)).shadowRadius(5).moveX(-move).moveY(-move).gravity().shadowColor(UIColor.grayColor()).duration(1)
.completion({
}).animate()
}
}
func swipeGuestureDidSwipeRight(gesture: UISwipeGestureRecognizer) {
print("Swiped right")
let subview = self.subviews[0]
subview.moveX(-60).duration(1).animate()
}
}
Example
class ExampleController: UIViewController {
var stackView: CardStackView!
override func viewDidLoad() {
super.viewDidLoad()
stackView = CardStackView(frame: CGRect(x: 20, y: 80, width: 200, height: 200))
stackView.dataSource = [UIImage(named: "2008")!, UIImage(named: "2008")!]
self.view.addSubview(stackView)
}
}
self.view.bringSubviewToFront(yourSubview)
try this code for all your subviews and if it doesn't work try this in your controller class for your CardStackView.
Try to call setNeedsLayout for stackView:
override func viewDidLoad() {
super.viewDidLoad()
stackView = CardStackView(frame: CGRect(x: 20, y: 80, width: 200, height: 200))
stackView.dataSource = [UIImage(named: "2008")!, UIImage(named: "2008")!]
stackView.setNeedsLayout()
self.view.addSubview(stackView)
}
Related
I am following a tutorial to create a swift app that adds a number of views to a scroll view and then allows me to scroll between them. I have the app working and understand it for the most part. When I change the orientation of the device the views width don't get updated so I have parts of more than one view controller on the screen? Does anybody know how to fix this? From my understanding I need to call something in the viewsDidTransition method to redraw the views. Any help would be greatly appreciated. Thanks!
Here is what I have so far:
import UIKit
class ViewController: UIViewController {
private let scrollView = UIScrollView()
private let pageControl: UIPageControl = {
let pageControl = UIPageControl()
pageControl.numberOfPages = 5
pageControl.backgroundColor = .systemBlue
return pageControl
}()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
pageControl.addTarget(self,
action: #selector(pageControlDidChange(_:)),
for: .valueChanged)
scrollView.backgroundColor = .red
view.addSubview(scrollView)
view.addSubview(pageControl)
}
#objc private func pageControlDidChange(_ sender: UIPageControl){
let current = sender.currentPage
scrollView.setContentOffset(CGPoint(x: CGFloat(current) * view.frame.size.width,
y: 0), animated: true)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
pageControl.frame = CGRect(x: 10, y: view.frame.size.height - 100, width: view.frame.size.width - 20, height: 70)
scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height - 100)
if scrollView.subviews.count == 2 {
configureScrollView()
}
}
private func configureScrollView(){
scrollView.contentSize = CGSize(width: view.frame.size.width*5, height: scrollView.frame.size.height)
scrollView.isPagingEnabled = true
let colors: [UIColor] = [.systemRed, .systemGray, .systemGreen, .systemOrange, .systemPurple]
for x in 0..<5{
let page = UIView(frame: CGRect(x: CGFloat(x) * view.frame.size.width, y: 0, width: view.frame.size.width, height: scrollView.frame.size.height))
page.backgroundColor = colors[x]
scrollView.addSubview(page)
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
print("hello world")
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
pageControl.currentPage = Int(floorf(Float(scrollView.contentOffset.x) / Float(scrollView.frame.size.width)))
}
}
My swift code uses func addBox to add and append image views to the uiview controller. All I want to do is when one of the image views are tapped is for func viewClicked to be activated. Right now nothing is happening and nothing is being written into the debug area.
import UIKit
class ViewController: UIViewController {
var ht = -90
var ww = 80
var hw = 80
var arrTextFields = [UIImageView]()
var b7 = UIButton()
override func viewDidLoad() {
[b7].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
$0.backgroundColor = .systemOrange
}
b7.frame = CGRect(x: view.center.x-115, y: view.center.y + 200, width: 70, height: 40)
b7.addTarget(self, action: #selector(addBOx), for: .touchUpInside)
for view in self.arrTextFields {
view.isUserInteractionEnabled = true
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(viewClicked)))
}
}
#objc func viewClicked(_ recognizer: UITapGestureRecognizer) {
print("tap")
}
//func that adds imageview.
#objc func addBOx() {
let subview = UIImageView()
subview.isUserInteractionEnabled = true
arrTextFields.append(subview)
view.addSubview(subview)
subview.frame = CGRect(x: view.bounds.midX - 0, y: view.bounds.midY + CGFloat(ht), width: CGFloat(ww), height: 35)
subview.backgroundColor = .purple
ht += 50
arrTextFields.append(subview)
}
}
You need to enable user interation
for view in self.arrTextFields {
view.isUserInteractionEnabled = true
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(viewClicked)))
}
#objc func viewClicked(_ recognizer: UITapGestureRecognizer) {
print("tap")
}
i want to implement a singleton spinner, because any app that has a api needs a spinner. well I can code a spinner custom, but my problem is that I should code the next line ever if I want to show it.
let rosetaGIF = UIImage(named: "wheel.png")
let ind = MyIndicator(frame: CGRect(x: 0, y: 0, width: (rosetaGIF?.size.width)!/2, height: (rosetaGIF?.size.height)!/2), image: rosetaGIF!)
view.addSubview(ind)
view.alpha = 0.5
ind.startAnimating()
that's not good, because I must put this lines every time that I want to show the spinner, well, my spinner class is the next. I'm using swift 4.2
import UIKit
class MyIndicator: UIActivityIndicatorView {
let loadingView = UIView(frame: (UIApplication.shared.delegate?.window??.bounds)!)
let imageView = UIImageView()
let sizeView = UIViewController()
init(frame: CGRect, image: UIImage) {
super.init(frame: frame)
imageView.frame = bounds
imageView.image = image
imageView.contentMode = .scaleAspectFit
imageView.center = CGPoint(x: sizeView.view.frame.width/2, y: sizeView.view.frame.height/2)
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(imageView)
}
required init(coder: NSCoder) {
fatalError()
}
override func startAnimating()
{
isHidden = false
rotate()
}
override func stopAnimating()
{
isHidden = true
removeRotation()
}
private func rotate() {
let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = NSNumber(value: Double.pi * 1)
rotation.duration = 1
rotation.isCumulative = true
rotation.repeatCount = Float.greatestFiniteMagnitude
self.imageView.layer.add(rotation, forKey: "rotationAnimation")
}
private func removeRotation() {
self.imageView.layer.removeAnimation(forKey: "rotationAnimation")
}
}
what should I do for that the spinner will be singleton?
thanks
I suggest you create a util class and make that class Singleton.
import UIKit
class Util {
static let shared = Util()
private init(){}
var loader: MyIndicator?
func showLoader(view: UIView){
hideLoader()
let rosetaGIF = UIImage(named: "wheel.png")
loader = MyIndicator(frame: CGRect(x: 0, y: 0, width: (rosetaGIF?.size.width)!/2, height: (rosetaGIF?.size.height)!/2), image: rosetaGIF!)
view.addSubview(loader!)
view.alpha = 0.5
loader?.startAnimating()
}
func hideLoader(){
loader?.stopAnimating()
loader?.removeFromSuperview()
}
}
How to use this class
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Util.shared.showLoader(view: view) // for showing the loader
Util.shared.hideLoader() // for hiding the loader
}
}
Create a Base Controller with your spinner, then inherit other Controllers from you Base Controller and show the spinner whenever you want.
class BaseViewController: UIViewController {
var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: (UIScreen.main.bounds.width-40)/2, y: (UIScreen.main.bounds.height-40)/2, width: 40, height: 40))
activityIndicator.transform = CGAffineTransform(scaleX: 2, y: 2)
activityIndicator.color = .darkGray
view.addSubview(activityIndicator)
}
}
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating() // you can show the spinner wherever you want.
}
}
hope this will help to you.
I am trying to make a custom jump bar which can be attached to UITableView.
What I want to achieve now is if user touches A and slides through Z, I want print out A, B, C, D..., Z. Currently, it only prints A, A, A...A. Is there anyway I can achieve it?
Each letter is UIButton subview of UIView.
class TableViewJumpBar{
let tableView: UITableView
let view: UIView
let jumpBar: UIView!
private var jumpIndexes: [Character]
init(tableView: UITableView, view: UIView){
self.view = view
self.tableView = tableView
jumpBar = UIView(frame: .zero)
jumpBar.backgroundColor = UIColor.gray
let aScalars = "A".unicodeScalars
let aCode = aScalars[aScalars.startIndex].value
jumpIndexes = (0..<26).map {
i in Character(UnicodeScalar(aCode + i)!)
}
}
func setFrame(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat){
guard jumpIndexes.count > 0 else{
print("Jump indexes cannot be empty")
return
}
//Remove jumpbar and remove all subviews
jumpBar.removeFromSuperview()
for subView in jumpBar.subviews{
subView.removeFromSuperview()
}
jumpBar.frame = CGRect(x: x, y: y, width: width, height: height)
let height = height/CGFloat(jumpIndexes.count)
for i in 0..<jumpIndexes.count{
let indexButton = UIButton(frame: CGRect(x:CGFloat(0.0), y: CGFloat(i)*height, width: width, height: height))
indexButton.setTitle(String(jumpIndexes[i]), for: .normal)
indexButton.addTarget(self, action: #selector(jumpIndexButtonTouched(_:)), for: .allEvents)
jumpBar.addSubview(indexButton)
}
self.view.addSubview(jumpBar)
}
///Touch has been begun
#objc private func jumpIndexButtonTouched(_ sender: UIButton!){
print(sender.titleLabel?.text)
}
this code works and print start and finish characters.
class ViewController: UIViewController {
#IBOutlet weak var myTableView: UITableView!
#IBOutlet weak var myView: UIView!
var startChar = ""
var finishChar = ""
override func viewDidLoad() {
super.viewDidLoad()
let table = TableViewJumpBar(tableView: myTableView, view: myView)
table.setFrame(x: 0, y: 0, width: 100, height: self.view.frame.size.height)
print(myView.subviews[0].subviews)
setPanGesture()
}
func setPanGesture() {
let pan = UIPanGestureRecognizer(target: self, action: #selector(panRecognized))
self.myView.addGestureRecognizer(pan)
}
#objc func panRecognized(sender: UIPanGestureRecognizer) {
let location = sender.location(in: myView)
if sender.state == .began {
for button in myView.subviews[0].subviews {
if let button = button as? UIButton, button.frame.contains(location), let startCharacter = button.titleLabel?.text {
self.startChar = startCharacter
}
}
} else if sender.state == .ended {
for button in myView.subviews[0].subviews {
if let button = button as? UIButton, button.frame.contains(location), let finishCharacter = button.titleLabel?.text {
self.finishChar = finishCharacter
}
}
print("start with \(startChar), finish with \(finishChar)")
}
}
}
In your code I delete Button action. you can use labels instead Buttons.
class TableViewJumpBar {
let tableView: UITableView
let view: UIView
let jumpBar: UIView!
private var jumpIndexes: [Character]
init(tableView: UITableView, view: UIView){
self.view = view
self.tableView = tableView
jumpBar = UIView(frame: .zero)
jumpBar.backgroundColor = UIColor.gray
let aScalars = "A".unicodeScalars
let aCode = aScalars[aScalars.startIndex].value
jumpIndexes = (0..<26).map {
i in Character(UnicodeScalar(aCode + i)!)
}
}
func setFrame(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat){
guard jumpIndexes.count > 0 else{
print("Jump indexes cannot be empty")
return
}
jumpBar.removeFromSuperview()
for subView in jumpBar.subviews{
subView.removeFromSuperview()
}
jumpBar.frame = CGRect(x: x, y: y, width: width, height: height)
let height = height/CGFloat(jumpIndexes.count)
for i in 0..<jumpIndexes.count{
let indexButton = UIButton(frame: CGRect(x:CGFloat(0.0), y: CGFloat(i)*height, width: width, height: height))
indexButton.setTitle(String(jumpIndexes[i]), for: .normal)
jumpBar.addSubview(indexButton)
}
self.view.addSubview(jumpBar)
}
}
Some output results:
I am getting a flicker (likely view controller not fully loaded) for a split second upon first load on my second view controller. I've tried to preset through a segue as well as setting it in a custom initializer prior to load. The affect that I've created is to keep the same background for each view controller. Note, that this only occurs upon first transition over to the view controller. It doesn't happen after the first segue.
Help is greatly appreciated. I'm including the applicable code below.
First View Controller:
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate, UIScrollViewDelegate
{
// Background Image
var backgroundImage: UIImageView!
var backgroundImages: [String] = ["canyon.jpeg","channel.jpeg","glacier.jpeg","northernlights.jpeg", "jungle"]
var currentBackground = 0
init(_ coder: NSCoder? = nil)
{
// Logic Goes Here
if let coder = coder
{
super.init(coder: coder)!
} else
{
super.init(nibName: nil, bundle: nil)
}
}
required convenience init(coder: NSCoder)
{
self.init(coder)
}
override func viewDidLoad()
{
setUpView()
super.viewDidLoad()
}
func setUpView()
{
backgroundImage = UIImageView(frame: CGRect( x: 0 , y: 0, width: ScreenWidth, height: ScreenHeight))
backgroundImage.image = UIImage(named: backgroundImages[currentBackground])
backgroundImage.alpha = 1
let playButtonPercent:CGFloat = 0.75
playButton = UIButton(frame: CGRect(x: (ScreenWidth * 0.5) - (ScreenWidth * playButtonPercent / 2), y: (ScreenHeight * (3 / 4)) - (ScreenHeight * (15/480)), width: (ScreenWidth * playButtonPercent), height: ScreenHeight * (30/480)))
playButton.setTitle("PLAY", for: UIControlState())
playButton.addTarget(self, action: #selector(self.playRelease), for: .touchUpInside)
self.view.addSubview(backgroundImage)
self.view.addSubview(playButton)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if (segue.identifier == "Play")
{
if let nextViewController = (segue.destination as? GameListViewController)
{
nextViewController.currentBackground = currentBackground
nextViewController.backgroundImage.image = UIImage(named: self.backgroundImages[currentBackground])
}
}
}
func playRelease()
{
// Segue
performSegue(withIdentifier: "Play", sender: self)
}
}
View Controller 2:
class GameListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate
{
let backgroundImages = ["canyon.jpeg","channel.jpeg","glacier.jpeg","northernlights.jpeg", "jungle"]
var currentBackground = 0
var backgroundImage: UIImageView!
override func viewDidLoad()
{
backgroundImage = UIImageView(frame: CGRect( x: 0 , y: 0, width: ScreenWidth, height: ScreenHeight))
super.viewDidLoad()
setUpView()
}
}
func setUpView()
{
backgroundImage.image = UIImage(named: backgroundImages[currentBackground])
backgroundImage.alpha = 1
self.view.addSubview(backgroundImage)
view.sendSubview(toBack: backgroundImage)
}
}