Search code examples
iosswiftsprite-kiticarousel

iCarousel on Game Swift+SpriteKit not working properly


I'm trying to add an iCarousel to my game that will be displayed in the MainMenu, where you will be able to select an item and unlock others while you play and do more progress within the game.

Tried looking into a few tutorials but i still have some problems and i can't figure it out.

This is the post I based my current code on and also this github

But the structure on that "SpriteKit iCarousel" github is different than mine too.

I can make the carousel be displayed but doesn't even work, and is also located on top-left of the screen like "stucked" for some reason.

So this is my GameViewController.swift code:

import UIKit
import SpriteKit
import GameKit

class GameViewController: UIViewController, iCarouselDataSource, iCarouselDelegate {


    var imageArray: NSMutableArray = NSMutableArray()
    var selectedIndex: Int!
    var carousel : iCarousel!

    deinit{
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }

    func showCarousel(){
        carousel.hidden = false
    }
    func hideCarousel(){
        carousel.hidden = true
    }

    override func awakeFromNib(){
        super.awakeFromNib()
        self.imageArray = NSMutableArray(array: ["white","white2","white3"])
    }

    func carousel(carousel:iCarousel, didSelectItemAtIndex index:NSInteger)   {

        let scene = MenuScene(size:self.view.bounds.size)
        scene.imageName = self.imageArray[index] as! String
        self.hideCarousel()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        self.authenticateLocalPlayer()
        self.setupCarousel()
    }

    func setupCarousel() {
        carousel = iCarousel()
        carousel.dataSource = self
        carousel.delegate = self
        carousel.type = .Linear
        carousel.reloadData()

        let spriteKitView = SKView()
        spriteKitView.frame = CGRectMake(0, 0, 250, 250)
//        self.view.insertSubview(spriteKitView, belowSubview: self.carousel) // this is showing an empty gray box 
        self.view.addSubview(self.carousel)

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.showCarousel), name: "showBallPicker", object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.hideCarousel), name: "hideBallPicker", object: nil)
    }

    func carousel(carousel: iCarousel, valueForOption option: iCarouselOption, withDefault value: CGFloat) -> CGFloat{

        if (option == .Spacing){
            return value * 2
        }

        return value
    }

    func carousel(carousel: iCarousel, viewForItemAtIndex index: Int, reusingView view: UIView?) -> UIView {
        var imageView: UIImageView!

        if view == nil {
            imageView = UIImageView(frame: CGRectMake(0, 0, 250, 250))
            imageView.backgroundColor = UIColor.redColor()
            imageView.contentMode = .ScaleAspectFill
        }else{
            imageView = view as! UIImageView
        }

        imageView.image = UIImage(named: "\(imageArray.objectAtIndex(index))")

        return imageView
    }

    func numberOfItemsInCarousel(carousel: iCarousel) -> Int {
        return imageArray.count
    }

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        let skView = self.view as! SKView

        if skView.scene == nil {
            skView.showsFPS = true
            skView.showsNodeCount = true
            skView.showsPhysics = false
            skView.multipleTouchEnabled = true

            let menuScene = MenuScene(size: CGSizeMake(375,667))
            menuScene.scaleMode = .AspectFill
            menuScene.imageName = self.imageArray[0] as! String

            self.hideCarousel()
            skView.presentScene(menuScene)
        }
    }

    override func viewWillDisappear(animated: Bool) {
        if let skView = self.view as? SKView {
            skView.presentScene(nil)
        }
    }

    override func viewDidDisappear(animated: Bool) {
        if let skView = self.view as? SKView {
            skView.presentScene(nil)
        }
    }

    override func prefersStatusBarHidden() -> Bool {
        return true
    }
}

And this is what i'm doing on MenuScene.swift

var childNode = SKSpriteNode()
var imageName = "white"{
    didSet{
        self.childNode.texture = SKTexture(imageNamed: imageName)
    }
}

func showBallPicker(){
    NSNotificationCenter.defaultCenter().postNotificationName("showBallPicker", object: nil)
}

override init(size: CGSize) {
     // here im supposed to create a childNode SKSpriteNode, but for what? it only adds the same image of the carousel and that's it.
    self.childNode = SKSpriteNode(imageNamed: imageName)
    self.childNode.anchorPoint = CGPointZero
    self.childNode.zPosition = 30
    self.childNode.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
    self.addChild(self.childNode)

}

And then, on touchesEnded i'm calling from a button touch self.showBallPicker() to display the carousel.

As I said before, it's adding the carousel, but not even working and on top-left corner.

How can I achieve this? Once I can make it display properly, I can handle the rest.

Thanks.


Solution

  • You're not setting the frame of the carousel or setting constraints on it.

    Try changing your setupCarousel method to:

    func setupCarousel() {
            carousel = iCarousel()
            carousel.dataSource = self
            carousel.delegate = self
            carousel.type = .Linear
            carousel.reloadData()
            // turn off autoresizing mask
            carousel.translatesAutoresizingMaskIntoConstraints = false
            self.view.addSubview(self.carousel)
            // Add constraints
            carousel.topAnchor.constraintEqualToAnchor(self.topLayoutGuide.bottomAnchor).active = true
            carousel.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor).active = true
            carousel.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor).active = true
            carousel.bottomAnchor.constraintEqualToAnchor(self.view.bottomAnchor).active = true
    
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.showCarousel), name: "showBallPicker", object: nil)
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.hideCarousel), name: "hideBallPicker", object: nil)
    }
    

    Update

    These constraints will pin the carousel to the edges of your view controller's view. If you want to position it differently, you could, for example, replace those constraints with something like:

    carousel.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor).active = true
    carousel.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor, constant: -100).active = true
    carousel.widthAnchor.constraintEqualToAnchor(self.view.widthAnchor).active = true
    carousel.heightAnchor.constraintEqualToConstant(200.0).active = true
    

    This will center the carousel horizontally and place the carousel 100 points up from the center vertically. The carousel will be the same width as it's superview but will always be 200 points tall. You can look at Apple's documentation for more options for using anchors to create constraints.

    If you want to add labels to your carousel items you should do that in viewForItemAtIndex. You can create a simple UIView subclass that will hold a UIImageView and UILabel and return that instead of just a UIImageView. For example:

    class CarouselItem: UIView {
        let imageView:UIImageView =
        {
            let imageView = UIImageView()
            imageView.translatesAutoresizingMaskIntoConstraints = false
            imageView.contentMode = .ScaleAspectFill
            return imageView
        }()
    
        let label:UILabel =
        {
            let label = UILabel()
            label.adjustsFontSizeToFitWidth = true
            label.minimumScaleFactor = 0.5
            label.translatesAutoresizingMaskIntoConstraints = false
            label.textAlignment = .Center
            label.numberOfLines = 0
            return label
        }()
    
        override init(frame: CGRect)
        {
            super.init(frame: frame)
            self.commonInit()
        }
    
        required init?(coder aDecoder: NSCoder)
        {
            super.init(coder: aDecoder)
            self.commonInit()
        }
    
        func commonInit()
        {
            self.addSubview(self.imageView)
            self.addSubview(self.label)
    
            self.imageView.topAnchor.constraintEqualToAnchor(self.topAnchor).active = true
            self.imageView.centerXAnchor.constraintEqualToAnchor(self.centerXAnchor).active = true
            self.imageView.widthAnchor.constraintEqualToAnchor(self.widthAnchor).active = true
            self.imageView.heightAnchor.constraintEqualToAnchor(self.heightAnchor, multiplier: 0.9).active = true
    
            self.label.topAnchor.constraintEqualToAnchor(self.imageView.bottomAnchor, constant: 8.0).active = true
            self.label.centerXAnchor.constraintEqualToAnchor(self.centerXAnchor).active = true
        }
    }
    

    Now in viewForItemAtIndex

    func carousel(carousel: iCarousel, viewForItemAtIndex index: Int, reusingView view: UIView?) -> UIView {
            var carouselItem: CarouselItem!
    
            if view == nil {
                carouselItem = CarouselItem(frame: CGRectMake(0, 0, 250, 250))
                carouselItem.backgroundColor = UIColor.redColor()
            }else{
                carouselItem = view as! CarouselItem
            }
    
            carouselItem.imageView.image = UIImage(named: "\(imageArray.objectAtIndex(index))")
            carouselItem.label.text = "Some Text"
    
            return carouselItem
        }