Is there a way to get the default height for UITabBars? I want to avoid hardcoding the value but would also like to avoid creating an instance of one to only get the height. Below is how I'm currently getting the height, but it seems like there should be a more efficient way that doesn't involved creating a controller.
extension UITabBar {
private static var storedHeight: CGFloat?
#objc static var height: CGFloat {
get {
if let height = storedHeight { return height }
storedHeight = UITabBarController().tabBar.frame.size.height
return storedHeight ?? 0
}
}
}
Related
Note:
I am looking for alternate solution, Since I have tried changing lots of Sprite Kit properties values, and came upto conclusion that it is not possible with those changes. So I ain't posting any code. You can try the mentioned library for more information.
Original Question:
I am using BLBubbleFilters library to create Bubbles.
This library used magnetic property to pull align items to the centre of the screen. And each time the screen opens the positioning of each node is different.
What have I tried so far?
I have tried playing around with the library and its physics attributes(e.g. Magnetic property) to achieve my goal.
The goal is:
Place 'Family' node over and above all nodes, and rest of the nodes below it.
What I thought is to that, each node will have a property for example "priority(Integer)". That is, the Priority 0 nodes will be placed above all nodes, priority 1 nodes below 0 priority nodes and so on.
Since SpriteKit deals with Physics properties, is there is any such property or way to achieve my goal?
See, how the family bubble is at the Top.
I had to do something like that and I did By creating a custom SelectInterestsCollectionViewFlowLayOut and I added the bubbles in the UICollectionViewCells.
The result was this.
Every cell is a white square and inside I have an image view with corner radius 1/2 * height.
The difference is that in my case I had the same bubble size for all my bubbles.
My code is:
enum InterestBubbleType {
case A
case B
case C
case D
case E
func getYPosition(cellSideSize: CGFloat) -> CGFloat {
let spaceFromTopForTwoCellsOnTop: CGFloat = cellSideSize / 2
switch self {
case .A:
return cellSideSize
case .B:
return cellSideSize * 2
case .C:
return spaceFromTopForTwoCellsOnTop
case .D:
return cellSideSize + spaceFromTopForTwoCellsOnTop
case .E:
return 0
}
}
}
class SelectInterestsCollectionViewFlowLayOut: UICollectionViewFlowLayout {
var cache: [UICollectionViewLayoutAttributes] = []
var layoutAttributes: [UICollectionViewLayoutAttributes] = []
var cellSideSize: CGFloat!
var xPosition: CGFloat = 0
var itemPositions: [InterestBubbleType] = [.A,.C,.D,.E,.A,.B,.C,.D,.E,.A,.B,.C,.D,.A,.B,.C,.D,.E,.A,.B]
var positionsToIncreaseXPosition = [0,2,5,7,10,12,14,16]
override func prepare() {
cache = []
super.prepare()
//Prepare constants
self.xPosition = 0
cellSideSize = self.collectionView!.height() / 3
self.configureCasheForTwentyInterests()
}
private func configureCasheForTwentyInterests() {
for i in 0...(self.itemPositions.count - 1) {
let indexPath = IndexPath(item: i, section: 0)
let xPosition = self.xPosition
let frame = CGRect(x: xPosition, y: self.itemPositions[i].getYPosition(cellSideSize: self.cellSideSize), width: self.cellSideSize, height: self.cellSideSize)
let atribute = UICollectionViewLayoutAttributes(forCellWith: indexPath)
atribute.frame = frame
cache.append(atribute)
if self.positionsToIncreaseXPosition.contains(i) {
self.xPosition += self.cellSideSize
}
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
self.layoutAttributes = []
for uiCollectionViewLayoutAttribute in self.cache {
self.layoutAttributes.append(uiCollectionViewLayoutAttribute)
}
return layoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return layoutAttributes[indexPath.row]
}
override var collectionViewContentSize: CGSize {
let totalWidth = cellSideSize * 9
return CGSize(width: totalWidth, height: self.collectionView!.height())
}
}
Now how it works is that in my collection view there are 5 different y positions that a bubble can be inside the collection view and they are A,B,C,D,E and I also keep in mind when you need to move 1 more x position which is 1 * collectionViewWidth size.
You can configure the code to achive your own result.
In my case I had a fixed number of bubbles and a fixed view to show them. The reason I chose to do it with collection view was because if the number change you can just add positions to change x position in positionsToIncreaseXPosition and it will work.
I'm attempting to return two float values, width and height using a standard UIButton function. I added the "-> Float" but I am greeted with this error. If I change it to "-> Void" then I am left with Unexpected non-void return value in void function. What am i doing wrong here?
#IBOutlet weak var buttonValue: UIButton!
#IBAction func smallB(_ sender: Any) -> Float {
var inital = 2
let divisble = 2
var width: Float
var height: Float
if inital % divisble == 0
{
width = 0.14
height = 0.07
buttonValue.titleLabel?.text = "Big"
inital + 1
}
else
{
width = 0.28
height = 0.14
buttonValue.titleLabel?.text = "Small"
inital + 1
}
return width
return height
}
IBAction function can be invoked/called by user interaction on any UIElements like Button, Switch, Segment Controller. And its response (return type) goes back to user interface element, you may not get float values in your code, if your use IBAction and attach that action to your Interface Builder
Remove return type from your IBAction method, if you've connected method with your Interface Builder Button element. It would be meaningless for you to return value, if your action is generated from IBOutlet element.
#IBAction func smallB(_ sender: Any) {
// action contents
// also remove following return statements
// return width
// return height
}
But if you really want to return height & width (two float values) as response to this function call then you should call it programmatically, and change function declaration like:
func smallBTest(_ sender: Any) -> CGSize {
// action contents
//Update your return statement like
return CGSize(width: width, height: height)
}
let size: CGSize = smallBTest(<button instance > buttonValue)
print("Size: Width = \(size.width) -- Height = \(size.Height)")
// or you can try this as well
#IBAction func smallB(_ sender: Any) {
let size: CGSize = smallBTest(sender)
print("Size: Width = \(size.width) -- Height = \(size.Height)")
}
You cannot return value from IBAction method. So change your method as
#IBAction func smallB(_ sender: Any) {
// your logic
}
And remove return width, return height .
If you want to use width and height make them as global variables.
I have multiple custom pop up views that I call (to set it up, animate the pop up and animate the dismiss) but I want to have one variable that I can replace in the multiple functions that can be set by a switch statement with one variable. So I don't have 4+ of every function with the same code but different uiView names. How is that done please?
var myPopupView:NotePopUpView!
var myInformationPopUpView:InformationPopUpView!
var myDatePopUpView:DatePopUpView!
var myPainDiagramView:PainDiagramPopUpView!
func notePopUpCalled() {
if (myPopupView != nil) {
self.myPopupView.view.removeFromSuperview()
}
// making different size pop up for different screens
var popUpWidth = GlobalConstants.ScreenStats.screenWidth - 60
if UIScreen.mainScreen().bounds.size.width > 320 {
if UIScreen.mainScreen().scale == 3 {
popUpWidth = 320
} else {
popUpWidth = 320
}
} else {
popUpWidth = 283
}
self.myPopupView = NotePopUpView(frame: CGRect(x: 20, y: 350, width: popUpWidth, height: 334))
myPopupView.center = self.view.center
self.view.addSubview(myPopupView)
showAnimate()
}
Generally when it is necessary to apply one method on different types recommended to use generics.
func setupView<T: UIView>(view: T) {
//Make all setups
}
Also you can use as? operator to downcasting your view types to UIView.
Hope it helps.
Update
The issue seems to be resolved when using the designated collectionViewContentSize() method like so:
let contentSize = collectionViewContentSize()
Nevertheless, I would be very interested in an explanation behind this behaviour, so I've updated my question accordingly.
Original Question
I am trying to recreate the steps found in the first example of this article, but using Swift and Storyboards.
I have a custom UICollectionViewFlowLayout subclass with the following content:
import UIKit
class SpringyFlowLayout: UICollectionViewFlowLayout {
private lazy var animator: UIDynamicAnimator = {
return UIDynamicAnimator(collectionViewLayout: self)
}()
override func prepareLayout() {
super.prepareLayout()
let contentSize = collectionView!.contentSize
let items = super.layoutAttributesForElementsInRect(CGRect(origin: CGPointZero, size: contentSize)) as! [UICollectionViewLayoutAttributes]
if animator.behaviors.isEmpty {
for item in items {
let spring = UIAttachmentBehavior(item: item, attachedToAnchor: item.center)
spring.length = 0
spring.damping = 0.8
spring.frequency = 1.0
animator.addBehavior(spring)
}
}
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
return animator.itemsInRect(rect)
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
return animator.layoutAttributesForCellAtIndexPath(indexPath)
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
let delta = newBounds.origin.y - collectionView!.bounds.origin.y
for spring in animator.behaviors {
let items = spring.items as! [UICollectionViewLayoutAttributes]
if let attributes = items.first {
attributes.center.y += delta
animator.updateItemUsingCurrentState(attributes)
}
}
return false
}
I have the correct Layout class set in Storyboards. When I run the app, my Collection View is empty.
I have determined that the issue is in the following snipped:
override func prepareLayout() {
super.prepareLayout()
let contentSize = collectionView!.contentSize
let items = super.layoutAttributesForElementsInRect(CGRect(origin: CGPointZero, size: contentSize)) as! [UICollectionViewLayoutAttributes]
//...
}
Since I subclass UICollectionViewFlowLayout, I thought I can rely on the super implementation to lay out the elements for me, and then modify their attributes. But when I check contentSize, it reports the width correctly, but the height is 0.
This leads to an empty items array -> no behaviors in animator -> animator.itemsInRect(_) returning an empty array -> the empty Collection View.
I just can't seem to find out what I'm missing. There should be no need to override the contentSize() method, since I'm using a flow layout.
The issue it that collectionView doesn't yet have a content size, because it just started to prepare it's layout. I don't believe that calling collectionView!.contentSize will actually compute the size. The reason collectionViewContentSize() works is because it will compute the size using your other layout code.
Here's something you might find interesting. I noticed in Ash Furrow's example in viewDidAppear a call to collectionViewLayout.invalidateLayout. Ash notes this isn't necessary when using storyboards due a difference in timing of the first invocation of prepareLayout versus when storyboards are not used.
I tried invalidating the collection view layout in viewDidAppear along with your code and a storyboard. Here is what I found:
Test Scenarios:
1) collectionView!.contentSize without invalidateLayout = cv is EMPTY
2) collectionViewContentSize() without invalidateLayout = cv WORKS
3) collectionView!.contentSize with invalidateLayout = cv WORKS
4) collectionViewContentSize() with invalidateLayout = cv WORKS
FYI
I just ask myself why I can't do something like this directly under my Class Declaration in Swift:
let width = 200.0
let height = 30.0
let widthheight = width-height
I can not create a constant with 2 other constants. If I use this inside a function/method everything works fine.
Thanks
When you write let widthheight = width - height, this implicitly means let widthheight = self.width - self.height. In Swift, you're simply not allowed to use self until all its members have been initialised — here, including widthheight.
You have a little bit more flexibility in an init method, though, where you can write things like this:
class Rect {
let width = 200.0
let height = 30.0
let widthheight: Double
let widthheightInverse: Double
init() {
// widthheightInverse = 1.0 / widthheight // widthheight not usable yet
widthheight = width - height
widthheightInverse = 1.0 / widthheight // works
}
}
This is a candidate for a computed property as such:
class Foo {
let width = 200.0
let height = 30.0
var widthheight : Double { return width - height }
}
You might raise an issue of 'but it is computed each time'; perhaps your application will depend on a single subtraction done repeatedly - but not likely. If the subtraction is an issue, set widthheight in init()
For things like that, you could make use of class variables. The code would look like this:
class var width = 200.0
class var height = 30.0
class var widthheight = width - height
But when you try it, you will see a compiler error:
Class variables are not yet supported
I guess they haven't implemented that feature yet. But there is a solution for now. Just move your declarations outside the class declaration, like following:
let width = 200.0
let height = 30.0
let widthheight = width - height
class YourClass { ...