swift global constants: cannot use another constant for initialization - ios

Here is what I am trying to do:
class ViewController: UIViewController {
let screenRect: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenRect.width;
let screenHeight = screenRect.height;
let screenX = screenRect.origin.x
let screenY = screenRect.origin.y
override func viewDidLoad() {
...and so on
Swift allows me to declare screenRect.
However, it does not allow me to declare any other constants using this. It shows me the error: 'ViewController.Type' does not have a member named 'screenRect'
How do I define these constants and why does not swift allow me to use another constnt to define them.

The consts do not know about the other global consts because they are not initialized at this point. What you could do though is to use computed properties:
class ViewController: UIViewController {
var screenRect: CGRect { return UIScreen.mainScreen().bounds }
var screenWidth: CGFloat { return self.screenRect.origin.x }
}
This only works with vars, due to the nature of it. This should be considered depending on the use case. If you have to call it often, it might not be the wisest way in regards to performance.

Its not possible to access self before initialisation process gets completed, therefore its giving error.

What probably happens is that during instantiation and initialization it's not guaranteed that properties are initialized in the same order as you have defined them in the code, so you cannot initialize a property with a value retrieved from another property.
My suggestion is to move the property initializations in the init() method.
Addendum 1: as suggested by #Yatheesha, it's correct to say that self is not available until all properties have been initialized - see "Two-Phase Initialization" paragraph in the swift book, under "Initialization"
In this line (as well as in the ones after it):
let screenWidth = screenRect.width;
you are implicitly using self.
So the correct way is to use init() as follows:
let screenRect: CGRect
let screenWidth: NSNumber
let screenHeight: NSNumber
let screenX: NSNumber
let screenY: NSNumber
init(coder aDecoder: NSCoder!) {
let bounds = UIScreen.mainScreen().bounds
screenRect = bounds
screenWidth = bounds.width
screenHeight = bounds.height
screenX = bounds.origin.x
screenY = bounds.origin.y
super.init(coder:aDecoder)
}
Of course if there is more than one designated init(), you have to replicate that code on each. In that case it might be better to use #Andy's approach, using computed properties, if you prefer to avoid code duplication.
Addendum 2
Another obvious way (and probably the best one) is to avoid using self at all:
let screenRect: CGRect = UIScreen.mainScreen().bounds
let screenWidth = UIScreen.mainScreen().bounds.width;
let screenHeight = UIScreen.mainScreen().bounds.height;
let screenX = UIScreen.mainScreen().bounds.origin.x
let screenY = UIScreen.mainScreen().bounds.origin.y

Related

"removeFromSuperview" NEVER work on UIApplication?

How to remove subviews?
I am trying to integrate GIF by creating UIView and UIImageView programmatically.
It works fine to show GIF but when the function of hiding if is called, there is no response.
Here are the codes of both functions.
class CustomLoader: UIView {
static let instance = CustomLoader()
var viewColor: UIColor = .black
var setAlpha: CGFloat = 0.5
var gifName: String = ""
lazy var transparentView: UIView = {
let transparentView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
transparentView.backgroundColor = viewColor.withAlphaComponent(setAlpha)
transparentView.isUserInteractionEnabled = false
return transparentView
}()
lazy var gifImage: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.contentMode = .scaleAspectFit
gifImage.center = transparentView.center
gifImage.isUserInteractionEnabled = false
gifImage.loadGif(name: gifName)
return gifImage
}()
func showLoaderView() {
self.addSubview(self.transparentView)
self.transparentView.addSubview(self.gifImage)
self.transparentView.bringSubview(toFront: self.gifImage)
UIApplication.shared.keyWindow?.addSubview(transparentView)
}
func hideLoaderView() {
self.transparentView.removeFromSuperview()
}
}
A couple of thoughts:
I’d suggest you add a breakpoint or a logging statement in hideLoaderView and make sure you’re getting to that line.
You should make the init method to this class private to make sure you’re not calling hideLoaderView on some separate instance. When dealing with singletons, you want to make sure you can’t accidentally create another instance.
But I tested your code, and it works fine. Your problem probably rests with where and how you call this (and making init private, you might find where you might be using it inappropriately).
In the comments below, you said:
I simply call the function "CustomLoader().hideLoaderView()" Both are being called technically. What do you mean by "where I using it inappropriately?"
That is the root of the problem.
The CustomLoader() of CustomLoader().hideLoaderView() will create a new instance of CustomLoader with its own transparencyView, etc., which is precisely what the problem is. You’re not hiding the old view that was presented earlier, but trying to hide another one that you just created and was never displayed.
If you instead use that static, e.g. CustomLoader.instance.showLoaderView() and CustomLoader.instance.hideLoaderView(), then the problem will go away. Then you will be hiding the same view that your previously showed.
By the way, a few other unrelated observations:
If this is a singleton or shared instance, the convention would be to call that static property shared, not instance.
By the way, you aren’t using this CustomLoader as a UIView, so I’d not make it a UIView subclass. Don’t make it a subclass of anything.
You would obviously eliminate that self.addSubview(transparentView) line, too.
The bringSubview(toFront:) call is unnecessary.
You should avoid referencing UIScreen.main.bounds. You don’t know if your app might be in multitasking mode (maybe this isn’t an issue right now, but it’s the sort of unnecessary assumption that will cause problems at some later date). Just refer to the bounds of the UIWindow to which you’re adding this. You should also update this frame when you show this view, not when you create it (in case you changed orientation in the intervening time, or whatever).
By the way, using keyWindow is discouraged in iOS 13 and later, so you might eventually want to remove that, too.
When adding the gifImage (which I’d suggest renaming to gifImageView because it’s an image view, not an image), you should not reference the center of its superview. That’s the coordinate of the transparent view in its super view’s coordinate system, which could be completely different than the transparent view’s own coordinate system. In this case, it just happens to work, but it suggests a fundamental misunderstanding of view coordinate systems. Reference the bounds of the transparentView, not its center.
If you’re going to expose viewColor and setAlpha, you should pull the setting of the transparentView’s color out of the lazy initializer and into showLoaderView, at the very least. Right now, if you show the loader once, and then change the color, and try to show it again, you won’t see the new color.
The same issue applies with the gif image. So, I’d move that to the didSet observer.
Thus, pulling this all together:
class CustomLoader{
static let shared = CustomLoader()
private init() { }
var dimmingColor: UIColor = .black
var dimmingAlpha: CGFloat = 0.5
var gifName: String = "" { didSet { gifImage.loadGif(name: gifName) } }
lazy var transparentView: UIView = {
let transparentView = UIView()
transparentView.isUserInteractionEnabled = false
return transparentView
}()
lazy var gifImageView: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.contentMode = .scaleAspectFit
gifImage.isUserInteractionEnabled = false
return gifImage
}()
func showLoaderView() {
guard let window = UIApplication.shared.keyWindow else { return }
transparentView.frame = window.bounds
transparentView.backgroundColor = dimmingColor.withAlphaComponent(dimmingAlpha)
gifImageView.center = CGPoint(x: transparentView.bounds.midX, y: transparentView.bounds.midY)
transparentView.addSubview(gifImageView)
window.addSubview(transparentView)
}
func hideLoaderView() {
transparentView.removeFromSuperview()
}
}
Why you are using transparentView while you are have a CustomLoader instance view
Try to use this
class CustomLoader: UIView {
static let instance = CustomLoader()
var viewColor: UIColor = .black
var setAlpha: CGFloat = 0.5
var gifName: String = ""
init() {
super.init(frame: UIScreen.main.bounds)
backgroundColor = viewColor.withAlphaComponent(setAlpha)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var gifImage: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.backgroundColor = .red
gifImage.contentMode = .scaleAspectFit
gifImage.center = center
gifImage.isUserInteractionEnabled = false
gifImage.loadGif(name: gifName)
return gifImage
}()
func showLoaderView() {
addSubview(self.gifImage)
UIApplication.shared.keyWindow?.addSubview(self)
}
func hideLoaderView() {
removeFromSuperview()
}
}

Property-like closures for local variables

I didn't found answer for my question in swiftbook.
Is this possible to create property-like closure for local variable in swift? I mean smt like further snippet:
func someFunc() {
// here goes our closure
var myRect:CGRect {
var x = 10
var y = 20
var width = 30
var heigth = 40
myRect = CGPointMake(x,y,width,heigth)
}
}
I have complexity evaluation of UI elements position. This trick should make my code much readable
This is called read-only computed property where you can omit the getter to simplify declaration:
var myRect: CGRect {
let x:CGFloat = 10
let y:CGFloat = 20
let width:CGFloat = 30
let height:CGFloat = 40
return CGRectMake(x, y, width, height)
}
Read-Only Computed Properties
A computed property with a getter but no setter is known as a
read-only computed property. A read-only computed property always
returns a value, and can be accessed through dot syntax, but cannot be
set to a different value.
NOTE
You must declare computed properties—including read-only computed
properties—as variable properties with the var keyword, because their
value is not fixed. The let keyword is only used for constant
properties, to indicate that their values cannot be changed once they
are set as part of instance initialization.
You can simplify the declaration of a read-only computed property by
removing the get keyword and its braces:
Documentation Swift Conceptual Properties
Why not try this way?
fun someFunc() {
var myRect = {() -> CGRect in
let x:CGFloat = 10.0
let y:CGFloat = 20.0
let width:CGFloat = 30.0
let height:CGFloat = 40.0
return CGRectMake(x,y,width,height)
}
myRect() //Call it
}
EDIT I think if there are some requirements to calculate some points position like maxElement use closure is good to save some small functions.

Using delegates: unexpectedly found nil while unwrapping an Optional value

I have two ViewControllers, assume one is named MainVC and the other one is named GetCameraRollImageViewController. In the second one, there is a UIScrollView which holds a UIImageView, since I'm using segue to show the second view controller, I've to use its delegate in the MainVC:
class ViewController: UIViewController, GetTextDelegate, GetCameraRollImageDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
//
//other codes
//
#IBAction func setNewImageFromCameraRoll(segue:UIStoryboardSegue) {
if let newImageVC = segue.sourceViewController as? GetCameraRollImageViewController{
var scale:CGFloat = 1.0/newImageVC.scrollView.zoomScale;
var visibleRect:CGRect!
visibleRect.origin.x = newImageVC.scrollView.contentOffset.x * scale;
visibleRect.origin.y = newImageVC.scrollView.contentOffset.y * scale;
visibleRect.size.width = newImageVC.scrollView.bounds.size.width * scale;
visibleRect.size.height = newImageVC.scrollView.bounds.size.height * scale;
var cr:CGImageRef = CGImageCreateWithImageInRect(newImageVC.inputImage.image?.CGImage,visibleRect)
var cropped:UIImage = UIImage(CGImage: cr)!
imageView.image = cropped
}
}
}
The GetCameraRollImageViewController code:
import UIKit
protocol GetCameraRollImageDelegate{
//
}
class GetCameraRollImageViewController: UIViewController, UIScrollViewDelegate {
var delegate:GetCameraRollImageDelegate? = nil
var inputImageDelegate:UIImage!
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var inputImage:UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.scrollView.minimumZoomScale = 0.5
self.scrollView.maximumZoomScale = 6.0
self.scrollView.contentSize = self.inputImage.frame.size;
self.scrollView.delegate = self
inputImage.image = inputImageDelegate
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return self.inputImage
}
}
But when I trigger setNewImageFromCameraRoll() it crashes the app with this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Note: Bothe scrollView and inputImage are bounded, and I'm not reading them in the viewDidLoad(), when they are empty.
I'm not 100% but I believe your problem is that the scrollView has not yet been created. Try passing the image to the new view controller and doing your resizing in the viewDidLoad of GetCameraRollImageViewController.
Your code has a number of problems.
First of all, why do you have an IBAction that takes a segue as a parameter?
Usually what you do is invoke a segue through whatever method, and then implement the preformSegue method. That method gets called whenever a segue is triggered in your view controller.
In performSegue you can get a pointer to the destination view controller and pass parameters to it.
It isn't clear to me how your setNewImageFromCameraRoll method is being called, or how it gets a segue object. It also doesn't make sense to me that you're using the source view controller from the segue. I suspect the method doesn't work as you expected.
The next problem has to do with what you're trying to do with the view controller you get from the segue. You're trying to manipulate it's views directly. Don't do that. You should treat a view controller's views as private. Instead you should add properties to your view controller that expose the information you need, and reference those properties.
Messing around with another view controller's views violates the principle of encapsulation. It also frequently fails because the other view controller's views don't get created until it's displayed on-screen.
According to me you haven't connected delegate of V2 class to self in MainVC
"newImageVC?.delegate=self"
[Updated based on comments]
The line
var visibleRect:CGRect!
assigns visibleRect to nil and thus the subsequent line of
visibleRect.origin.x = newImageVC.scrollView.contentOffset.x * scale;
will then dereference visibleRect, find nil and produce the exception. You need to allocate a CGRect and assign it to visibleRect.
[Original Answer]
Is visibleRect bound? It doesn't look to be. If that is bound, then you'll notice other uses of !... You need to bind your outlets (typically in InterfaceBuilder):
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var inputImage:UIImageView!
Perhaps scrollView is not bound and thus, because it is implicitly unwrapped (the ! operator), when you reference it as nil your program will fatal.
Use:
var visibleRect =
CGRectMake (newImageVC.scrollView.contentOffset.x * scale,
newImageVC.scrollView.contentOffset.y * scale,
newImageVC.scrollView.bounds.size.width * scale,
newImageVC.scrollView.bounds.size.height * scale)

UICollectionViewFlowLayout: collectionView!.contentSize vs collectionViewContentSize

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

Problems with Swift Declaration

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 { ...

Resources