UITableViewCell awakeFromNib wrong frame size - objective-c

I have a UITableViewCell with constraints so that the cells layout correctly on iPhone 6 and iPhone 6+.
The cell contents correctly resize correctly. However, I need to draw a sublayer gradient over one of the views inside. In order to do so, I'm using awakeFromNib to draw the sublayer.
AwakeFromNib is giving the size of the frame prior to it autoresizing and therefore the gradient subview isn't the right size.
If I use layoutSubviews, the first time it's called, the size is also wrong and the cell needs to be scrolled before it has the right size; so that method also isn't working
What method should I be using to get the right frame size in order to draw correctly?

This code worked for me, is written in Swift.
import UIKit
import QuartzCore
class DemoTableViewCell: UITableViewCell {
#IBOutlet weak var gradientView: UIView!
private let gl = CAGradientLayer()
override func awakeFromNib() {
super.awakeFromNib()
// set gradient
let colors: [AnyObject] = [
UIColor.clearColor().CGColor,
UIColor(white: 0, alpha: 0.8).CGColor
]
gl.colors = colors
gradientView.layer.insertSublayer(gl, atIndex: 0)
}
override func layoutSubviews() {
super.layoutSubviews()
// update gradient
gl.frame = gradientView.bounds
}
}

during awakeFromNib the frame layouts do not appear. On your custom Cell you can either do this
-(void)drawRect:(CGRect)rect
{
[super drawRect:rect];
// Enter Custom Code
}
Or you can override applyLayoutAttributes method like this
-(void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[super applyLayoutAttributes:layoutAttributes];
// Enter custom Code here
}
applyLayoutAttributes is called before drawRect method but still has the size parameteres of rect.

#angshuk answer is perfect where if anyone wants to modify UI inside UITableViewCell. Like in my case I was trying to add a different layer on runtime to an UIView, placed inside UITableViewCell using storyboard/autolayout. Tried the same thing inside awakeFromNib() & layoutSubviews() also. Nothing worked.
Here is the same code using Swift. By the way I am using Xcode 7.3.1 with Swift 2.2.
import UIKit
class InviteMainCellClass: UITableViewCell {
#IBOutlet weak var shareCodeLblSuperView: UIView! //Custom view added using IB/Autolayout
override func awakeFromNib() {
super.awakeFromNib()
//shareCodeLblSuperView.addDashedBorderToView(self.shareCodeLblSuperView, viewBorderColor: UIColor.whiteColor(), borderWidth: 1.0) //Not worked
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
shareCodeLblSuperView.addDashedBorderToView(self.shareCodeLblSuperView, viewBorderColor: UIColor.whiteColor(), borderWidth: 1.0) //Worked
}
override func layoutSubviews() {
super.layoutSubviews()
//shareCodeLblSuperView.addDashedBorderToView(self.shareCodeLblSuperView, viewBorderColor: UIColor.whiteColor(), borderWidth: 1.0)//Not worked
}
Hope this helped. Thanks.

Related

Touch and move CAShapeLayer over UIImageView

I am trying to custom annotate images on image view using CAShapeLayers. This include adding shapes(like rectangle, ellipse, line) over the image view, touch them, move them around and pinch-zoom them. I am able to recognise the touch of the layer over the image view, but unsuccessful when tried to move it around. As I try to move the layer, it flies to a corner.
Code and output as below:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func drawRectangle(_ sender: UIButton) { // to initially place the shape at the centre of the image view.
drawRectangleWithRect(CGRect(x: imageView.bounds.midX - 100, y: imageView.bounds.midY - 100, width: 200, height: 200))
}
func drawRectangleWithRect(_ rect: CGRect) {
let path = UIBezierPath(rect: rect)
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
//shapeLayer.bounds = (shapeLayer.path?.boundingBox)!
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = 1
imageView.layer.addSublayer(shapeLayer)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let point = touch!.location(in: imageView)
guard let sublayers = imageView.layer.sublayers else { return }
for sublayer in sublayers {
if let sublayer = sublayer as? CAShapeLayer, let path = sublayer.path {
//if path.contains(point) {
sublayer.position = point
//}
}
}
}
}
Tapping on Rectangle button creates the layer at the centre of image view as above. But when I start to touch and move the layer just a bit it goes off to the right bottom corner and disappears.
Now tweaked a bit - uncommented the line shapeLayer.bounds = (shapeLayer.path?.boundingBox)! inside the method drawRectangleWithRect(_ rect: CGRect). Then tapping on the Rectangle button placed the layer at the left top of the view and not at the centre :( . But moving it around now made the layer move around with my finger (though there is a delay). But also that this made the layer move even when image view is touched outside the layer. I understand that some if condition inside the touchesMoved method is needed that properly recognises the layer area but the commented one in the above code did not work properly.
What am I missing to properly place the layer at centre of the view and move it around without any delay ?
Can I actually pinch zoom the layers effectively ?
What is the best recommended way to annotate images like this ? Adding layers with touch recognition or adding shapes as subviews with pan gestures ?

Full width NSCollectionViewFlowLayout with NSCollectionView

I have a NSCollectionViewFlowLayout which contains the following:
- (NSSize) itemSize
{
return CGSizeMake(self.collectionView.bounds.size.width, kItemHeight);
} // End of itemSize
- (CGFloat) minimumLineSpacing
{
return 0;
} // End of minimumLineSpacing
- (CGFloat) minimumInteritemSpacing
{
return 0;
} // End of minimumInteritemSpacing
I've tried to ways to make the layout responsive (set the width whenever resized). I've tried adding the following:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(onWindowDidResize:)
name: NSWindowDidResizeNotification
object: nil];
- (void) onWindowDidResize: (NSNotification*) notification
{
[connectionsListView.collectionViewLayout invalidateLayout];
} // End of windowDidResize:
And this works fine if I expand the collection view (resize larger). But if I attempt to collapse the view (resize smaller), I get the following exceptions:
The behavior of the UICollectionViewFlowLayout is not defined because:
The item width must be less than the width of the UICollectionView minus the section insets left and right values, minus the content insets left and right values.
The relevant UICollectionViewFlowLayout instance is <TestListLayout: 0x106f70f90>, and it is attached to <NSCollectionView: 0x106f76480>.
Any suggestions on how I can resolve this?
NOTE1: This is macOS and not iOS (even though the error message states UICollectionViewFlowLayout).
NOTE2: Even though I receive the warning/error the layout width works, but I would like to figure out the underlying issue.
I had the same problem but in my case I resized view. #Giles solution didn't work for me until I changed invalidation context
class MyCollectionViewFlowLayout: NSCollectionViewFlowLayout {
override func shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool {
return true
}
override func invalidationContext(forBoundsChange newBounds: NSRect) -> NSCollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds) as! NSCollectionViewFlowLayoutInvalidationContext
context.invalidateFlowLayoutDelegateMetrics = true
return context
}
}
Hope it helps someone as it took me couple of evenings to find solution
This is because you are using the didResize event, which is too late. Your items are too wide at the moment the window starts to shrink. Try using:
func shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool {
return true
}
...when overriding flow layout, to get everything recalculated.
The source code I posted in the question works fine as of macOS 10.14 with no issues. I added the following to my window which displays the collection view.
// Only Mojave and after is resizable. Before that, a full sized collection view caused issues as listed
// at https://stackoverflow.com/questions/48567326/full-width-nscollectionviewflowlayout-with-nscollectionview
if(#available(macOS 10.14, *))
{
self.window.styleMask |= NSWindowStyleMaskResizable;
} // End of macOS 10.14+

UICollectionView fails to honour zPosition and zIndex - iOS 11 only

I try to find a workaround for this radar: http://www.openradar.me/34308893
Currently in my - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes I'm doing the following:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC * 50), dispatch_get_main_queue(), ^{
self.layer.zPosition = 0;
});
But no matter how many NSEC_PER_MSEC I enter it doesn't work quite right, the scrolling indicator sometimes hides behind the headers, especially when ne headers appear. Is there another/better approach?
I had a similar problem where a header view in a UICollectionView was getting rendered above another view that had it's zIndex set > 0 in a custom UICollectionViewFlowLayout.
The answer for me was to set the header view layer's zPosition in willDisplaySupplementaryView:
override public func collectionView(_ collectionView: UICollectionView,
willDisplaySupplementaryView view: UICollectionReusableView,
forElementKind elementKind: String,
at indexPath: IndexPath) {
if elementKind == UICollectionView.elementKindSectionHeader && type(of: view) == CollectionViewSectionHeaderView.self {
view.layer.zPosition = -1
}
}
iOS11 doesn't seem to honor z-indexes in UICollectionViewFlowLayout subclasses.

Swift CAKeyframe Animation not displaying images

I have set up a very simple Single View Application (in Swift) using iOS 8.1. I have added one UIImageView to the main view controller view. I am trying to use CAKeyframeAnimation to animate a sequence of images. I was originally using the UIImageView animationImages property which worked fine but I need to be able to know precisely when the animation finishes, hence the move to CAKeyframeAnimation.
My code is as follows:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var animation : CAKeyframeAnimation!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let animationImages:[AnyObject] = [UIImage(named: "image-1")!, UIImage(named: "image-2")!, UIImage(named: "image-3")!, UIImage(named: "image-4")!]
animation = CAKeyframeAnimation(keyPath: "contents")
animation.calculationMode = kCAAnimationDiscrete
animation.duration = 25
animation.values = animationImages
animation.repeatCount = 25
animation.removedOnCompletion = false
animation.fillMode = kCAFillModeForwards
self.imageView.layer.addAnimation(animation, forKey: "contents")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The problem is that the animation does not display any images and I just receive a blank screen. Is there something I am missing in the above code? How can I get the animation to display?
This line is never going to work:
animation.values = animationImages
Change it to:
animation.values = animationImages.map {$0.CGImage as AnyObject}
The reason is that you are trying to animate the "contents" key of this layer. But that is the contents property. But the contents property must be set to a CGImage, not a UIImage. Your animationImages, by contrast, contains UIImages, not CGImages.
Thus you need to transform your array of UIImage into an array of CGImage. Moreover, you're trying to pass this array to Objective-C, where an NSArray must contain objects only; and since CGImage is not an object in Objective-C's mind, you need to cast each of them as an AnyObject. And that is what my map call does.

How to get continous call back when MKAnnotationView is dragged

I have MKAnnotationView which is drag able, and I have implemented didChangeDragState: delegate method for which I get callback at start and end of drag but not continuously. I want to track the current coordinate of annotation as it is dragged, please help me with some solution. Thank you.
The coordinate can be got from annotationview.coordinates :
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:
(MKAnnotationViewDragState)oldState
{
CLLocationCoordinate2D currentCoordinates = view.annotation.coordinate;
}
As far as I know, there isn't an Apple-provided way for you to do this, but you can achieve it through KVO (h/t to this answer).
You'll also need to manually determine the annotation view's coordinates, as the annotation's coordinates aren't updated until after it has entered MKAnnotationViewDragStateEnding.
The full solution looks like this:
// Somewhere in your code before the annotation view gets dragged, subscribe your observer object to changes in the annotation view's frame center
annotationView.addObserver(DELEGATE_OBJECT, forKeyPath: "center", options: NSKeyValueObservingOptions.New, context: nil)
// Then in the observer's code, fill out the KVO callback method
// Your observer will need to conform to the NSKeyValueObserving protocol -- all `NSObject`s already do
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
// Check that we're observing an annotation view. The `where` clause can be omitted if you're only observing changes in its frame, or don't care about extra calls
if let annotationView = object as? MKAnnotationView where keyPath == "center" {
// Defined below, `coordinateForAnnotationView` converts a CGPoint measured within a given MKMapView to the geographical coordinate represented in that location
let newCoordinate = coordinateForAnnotationView(pin, inMapView: self.mapView)
// Do stuff with `newCoordinate`
}
}
func coordinateForAnnotationView(annotationView: MKAnnotationView, inMapView mapView: MKMapView) -> CLLocationCoordinate2D {
// Most `MKAnnotationView`s will have a center offset, including `MKPinAnnotationView`
let trueCenter = CGPoint(x: annotationView.center.x - annotationView.centerOffset.x,
y: annotationView.center.y - annotationView.centerOffset.y)
return mapView.convertPoint(trueCenter, toCoordinateFromView: mapView)
}