I have a table view cell with a scroll view for scrolling through images. There are a couple of issues I'm having with it.
When the table view is shown, the cell with the scroll view and images shows like the first image and part of the second image and the paging doesn't work and it lets you scroll in any direction.
Once I scroll past this cell and then scroll back to it, it is displayed correctly and paging works like it should.
In cellForRowAtIndexPath()
if indexPath.section == 0 {
let imageCell = tableView.dequeueReusableCellWithIdentifier("imageCell", forIndexPath: indexPath) as! ImageCell
imageCell.selectionStyle = .None
if imageFiles.count > 0 {
if imageFiles.count == 1 {
imageCell.pageControl.hidden = true
}
imageCell.pageControl.numberOfPages = imageFiles.count
imageCell.pageControl.currentPage = 0
var index = 0
for photo in imageFiles {
var frame = CGRect()
frame.origin.x = (imageCell.imageScrollView.frame.size.width * CGFloat(index))
frame.origin.y = 0
frame.size = CGSizeMake(imageCell.imageScrollView.frame.size.width, imageCell.imageScrollView.frame.size.height)
let imageView = UIImageView(frame: frame)
imageView.image = photo
imageCell.imageScrollView.addSubview(imageView)
imageCell.imageScrollView.contentSize = CGSizeMake(imageCell.imageScrollView.frame.size.width * CGFloat(imageFiles.count), imageCell.imageScrollView.frame.size.height)
index += 1
}
} else {
imageCell.pageControl.hidden = true
let imageView = UIImageView(frame: imageCell.imageScrollView.frame)
imageView.image = UIImage(named: "placeholder")
imageCell.imageScrollView.addSubview(imageView)
}
My custom cell:
import UIKit
class ImageCell: UITableViewCell, UIScrollViewDelegate {
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var imageScrollView: UIScrollView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let pageWidth = scrollView.frame.size.width
pageControl.currentPage = Int(scrollView.contentOffset.x / pageWidth)
}
}
I was using SDWebImageDownloader. viewDidLoad() to download images to use in the scroll view but I wasn't reloading the table view data. Once I added that, this works perfectly.
func downloadImages() {
let downloader = SDWebImageDownloader.sharedDownloader()
for imageURL in images {
let url = NSURL(string: imageURL)
downloader.downloadImageWithURL(url, options: SDWebImageDownloaderOptions.UseNSURLCache, progress: nil,
completed: { (image, data, error, bool) -> Void in
self.imageFiles.append(image)
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
})
}
}
You are doing this:
frame.size = CGSizeMake(imageCell.imageScrollView.frame.size.width, imageCell.imageScrollView.frame.size.height)
I would guess that when your view initially loads, imageScrollView has not been fully laid out, so it doesn't have a valid size. Scrolling away from this cell & back to it after the view is fully loaded would result in the scroll view having a valid size.
You should validate that imageScrollView has the width you think it does, and if it doesn't, maybe use the width of the screen instead.
I have implemented a similar thing in one of my apps, so as another possible option, instead of putting a scroll view in your first cell, you could place a CollectionView in there instead and populate your collection view with cells of images. This would A) allow your layout to dynamically change with the view, and B) rather than loading up your scroll view with all the images, they can be dynamically loaded as needed.
Related
I have a custom UITableViewCell that displays a circular image on the left-hand side. Since the default UIImageView supplied with the UITableViewCell is the same height as the row, the images end up nearly touching. I'd like to shrink the image slightly to create some extra padding.
I was able to get this to work using the following code
override func layoutSubviews() {
super.layoutSubviews()
// Make the image view slightly smaller than the row height
self.imageView!.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
// Round corners
self.imageView!.layer.cornerRadius = self.imageView!.bounds.height / 2.0
self.imageView!.layer.borderWidth = 0.5
self.imageView!.layer.borderColor = UIColor.gray.cgColor
self.imageView!.layer.masksToBounds = true
self.imageView!.contentMode = .scaleAspectFill;
self.updateConstraints()
}
override func prepareForReuse() {
super.prepareForReuse()
self.imageView!.image = nil
self.layoutSubviews()
}
This works only for the cells displayed in the table view when it first appears on the screen. Once I scroll (i.e. dequeue a re-usable cell), the transform is no longer applied. The image below shows the left side of the table view. I've captured the region where the original cells transition to re-used cells.
For completeness, here is my tableView(cellForRowAt:) function
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.itemTableView.dequeueReusableCell(withIdentifier: "ItemCell") as! InventoryItemTableViewCell
if let items = self.displayedItems {
if indexPath.row < items.count {
let item = items[indexPath.row]
cell.item = item
cell.textLabel!.text = items[indexPath.row].partNumber
cell.detailTextLabel!.text = items[indexPath.row].description
if let quantity = items[indexPath.row].quantity {
cell.quantityLabel.text = "Qty: \(Int(quantity))"
}
else {
cell.quantityLabel.text = "Qty: N/A"
}
if let stringImageBase64 = item.imageBase64 {
let dataDecoded: Data = Data(base64Encoded: stringImageBase64, options: .ignoreUnknownCharacters)!
cell.imageView!.image = UIImage(data: dataDecoded)
}
else {
cell.imageView!.image = blankImage
}
}
}
return cell
}
I tried other methods such as playing with the image view's insets but this had no effect.
Question
Why is the transform being applied to the original cells when the table is created but not to any re-used cells? Should I be approaching this differently?
I was able to accomplish resizing the image using the following code. It seems the autolayout is applied after the transform, therefore overriding it.
override func layoutSubviews() {
// Layout all subviews except for the image view
super.layoutSubviews()
// Make the image view slightly smaller than the row height
self.imageView!.frame = CGRect(x: self.imageView!.frame.origin.x + 4,
y: self.imageView!.frame.origin.y + 4,
width: 56,
height: 56)
// Round corners
self.imageView!.layer.cornerRadius = self.imageView!.frame.height / 2.0
self.imageView!.layer.borderWidth = 0.5
self.imageView!.layer.borderColor = UIColor.gray.cgColor
self.imageView!.layer.masksToBounds = false
self.imageView!.clipsToBounds = true
self.imageView!.contentMode = .scaleAspectFit;
}
I am working on implementing a parallax effect for my UICollectionView. The effect thankfully is working. However, when I scroll the collection vertically for the very first time, there is a choppy effect that occurs. This ONLY happens on the first scroll through of the list. After that, the parallax effect is smooth while scrolling.
Below is my relevant code:
MyViewController.swift:
extension FeedViewController: UIScrollViewDelegate{
func scrollViewDidScroll(scrollView: UIScrollView) {
// Scroll navigation bar animation
let scrolledDown = scrollView.contentOffset.y > -self.topLayoutGuide.length
let newAlpha:CGFloat = scrolledDown ? 0.65 : 0.00
UIView.animateWithDuration(0.25) {
self.navBarBackground.alpha = newAlpha
}
// Parallax effect
for cell in collectionView.visibleCells() {
updateParallaxCell(cell)
}
}
func updateParallaxCell(cell: UICollectionViewCell) {
if let myCell = cell as? MyCustomCell {
myCell.updateParallaxEffect(collectionView, mainView: view)
}
}
}
MyCustomCell.swift
override func awakeFromNib() {
super.awakeFromNib()
backgroundImageOriginalOrigin = backgroundImage.frame.origin
}
func updateParallaxEffect(collectionView: UICollectionView, mainView view: UIView) {
let convertedRect = collectionView.convertRect(self.frame, toView: view) //frame of the cell within the coordinate system of the main view
let distanceFromCenter = CGRectGetHeight(self.frame) / 2 - CGRectGetMinY(convertedRect) //y coordinate distance of the cell from the center of the main view
let difference : CGFloat = 50.0 //maximum relative offset of the image within the cell
let move = -difference / 4 + (distanceFromCenter / self.frame.size.height) * difference // calculated relative offset of the image
let y = self.backgroundImageOriginalOrigin.y + move // newly calculated Y coordinate of the image
self.backgroundImage.frame.origin = CGPointMake(self.backgroundImageOriginalOrigin.x, y) // adjusting the image view frame
}
override func layoutSubviews() {
updateParallaxEffect(collectionView, mainView: mainView)
super.layoutSubviews()
}
Can anyone see what it is I'm doing wrong?
Try adding this to your viewDidLoad():
UICollectionView().setContentOffset(CGPointMake(0, 1), animated: true)
UICollectionView().setContentOffset(CGPointMake(0, 0), animated: false)
I have custom table cell. Each cell have different height. I want to give swipe delete button with cell height.
I have used view in cell. View height is cell height - 16. Top-bottom margin 8.
So please help me to do this.
Use this code in your Custom Cell Swift class
override func layoutSubviews() {
let fltHeight:CGFloat = 46
var subviews: [Any] = self.subviews
let subview: UIView? = subviews[0] as? UIView
if NSClassFromString("UITableViewCellDeleteConfirmationView") != nil {
if (subview?.isKind(of: NSClassFromString("UITableViewCellDeleteConfirmationView")!))! {
let deleteButtonView: UIView? = (subview?.subviews[0])
var buttonFrame: CGRect? = deleteButtonView?.frame
buttonFrame?.origin.x = (deleteButtonView?.frame.origin.x)!
buttonFrame?.origin.y = (deleteButtonView?.frame.origin.y)!
buttonFrame?.size.width = (deleteButtonView?.frame.size.width)!
buttonFrame?.size.height = fltHeight
deleteButtonView?.frame = buttonFrame!
// Placing at the center of the cell.
subview?.frame = CGRect(x: CGFloat((subview?.frame.origin.x)!),
y: CGFloat((subview?.frame.origin.y)! + ((subview?.frame.size.height)!-fltHeight)/2),
width: CGFloat((subview?.frame.size.width)!),
height: CGFloat(fltHeight))
deleteButtonView?.clipsToBounds = true
subview?.clipsToBounds = true
}
}
}
I'm sorry for the title, can not synthesize differently the issue. Here's the problem:
In my UICollectionView have some cells that want to put a shadow, they are arranged very close to each other which makes the shadow of each one reach the neighbor (first image), when what I want, is that it only reaches the background (second image).
What I've thought or tried:
I can not put a view behind the cells, adding their frames to it, and apply shadow in this view because the cells has dynamic movement (UIDynamics CollectionView Layout).
I tried, in the subclass of UICollectionViewLayout, put all these cells in the same z-index. Did not work. Find out why:
var zIndex: Int
(...) Items with the same value have an undetermined
order.
I would like some help with my problem, please. Thanks!
From the TheEye response, I decided to implement UIDecorationViews. All great now.
// MARK: At UICollectionViewCustomLayout:
public override init() {
super.init()
// Register the NIB of the view that will hold the shadows:
let nib = UINib(nibName: "Shadow", bundle: nil)
self.registerNib(nib, forDecorationViewOfKind: "shadow")
}
public override func layoutAttributesForDecorationViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let layoutAtt: UICollectionViewLayoutAttributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: "shadow", withIndexPath: indexPath)
layoutAtt.frame = (layoutAttributesForItemAtIndexPath(indexPath)?.frame)!
layoutAtt.zIndex = -1
return layoutAtt
}
public override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var atts = [UICollectionViewLayoutAttributes]()
let numberOfItems:Int = (self.collectionView?.numberOfItemsInSection(0))!
for index in 0..<numberOfItems {
let layoutItem = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: index, inSection: 0))!
let frameItem = layoutItem.frame
if CGRectIntersectsRect(frameItem, rect) {
atts.append(layoutAttributesForDecorationViewOfKind("shadow", atIndexPath: NSIndexPath(forItem: index, inSection: 0))!)
atts.append(layoutItem)
}
}
return atts
}
// MARK: At the Nib Class File:
// Here I created a additional view to append the shadow.
// Thats because awakeFromNib() is called before the UICollectionViewCustomLayout
// have a chance to layout it, but I want to make
// use of shadowPath to gain performance, thats why
// I make use of the additional UIView with autolayout stuffs.
#IBOutlet weak var shadowView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
shadowView.layer.shadowOffset = CGSizeMake(0, 0)
shadowView.layer.shadowColor = UIColor.yellowColor().CGColor
shadowView.layer.shadowRadius = 3
shadowView.layer.shadowOpacity = 1
let shadowFrame: CGRect = shadowView.layer.bounds
let shadowPath: CGPathRef = UIBezierPath(rect: shadowFrame).CGPath
shadowView.layer.shadowPath = shadowPath
shadowView.clipsToBounds = false
self.clipsToBounds = false
}
You could try to define the shadows as supplementary views, aligned with their respective cells, and give them a lower z order than the cells.
I'm trying to showing a list of place name, including it's photo using PFQueryTableViewController. It's included in ParseUI SDK from parse.com
I have managed to show the image. Unfortunately, when I change the UIImageView mode to Aspect fill, the image is become bigger than it should be.
here are the pictures:
https://dl.dropboxusercontent.com/u/86529526/pic_normal.png
https://dl.dropboxusercontent.com/u/86529526/pic_error.png
in pic_normal, you will see two cell, with two normal image.
in pic_error, you will the second cell was being overlaid by the first cell image.
can anyone help me to solve this problem? I also put my whole code here :
import UIKit
class TableViewController: PFQueryTableViewController, UISearchBarDelegate {
#IBOutlet var searchBar: UISearchBar!
// Initialise the PFQueryTable tableview
override init(style: UITableViewStyle, className: String!) {
super.init(style: style, className: className)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// Configure the PFQueryTableView
self.pullToRefreshEnabled = true
self.paginationEnabled = false
}
// Define the query that will provide the data for the table view
override func queryForTable() -> PFQuery {
// Start the query object
var query = PFQuery(className: "Places")
// query with pointer
query.includeKey("mainPhoto")
// Add a where clause if there is a search criteria
if searchBar.text != "" {
query.whereKey("name", containsString: searchBar.text)
}
// Order the results
query.orderByAscending("name")
// Return the qwuery object
return query
}
//override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject) -> PFTableViewCell? {
var cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomTableViewCell!
if cell == nil {
cell = CustomTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "CustomCell")
}
// Extract values from the PFObject to display in the table cell
if let name = object["name"] as? String{
cell.name.text = name
}
// display initial image
var initialThumbnail = UIImage(named: "question")
cell.photo.image = initialThumbnail
// extract image from pointer
if let pointer = object["mainPhoto"] as? PFObject {
cell.detail.text = pointer["photoTitle"] as? String!
if let thumbnail = pointer["photo"] as? PFFile {
cell.photo.file = thumbnail
cell.photo.loadInBackground()
}
}
cell.sendSubviewToBack(cell.photo)
// return the cell
return cell
}
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController].
var detailScene = segue.destinationViewController as! DetailViewController
// Pass the selected object to the destination view controller.
if let indexPath = self.tableView.indexPathForSelectedRow() {
let row = Int(indexPath.row)
detailScene.currentObject = objects[row] as? PFObject
}
}
override func viewDidLoad(){
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target:self, action:Selector("hideKeyboard"))
tapGesture.cancelsTouchesInView = true
tableView.addGestureRecognizer(tapGesture)
}
func hideKeyboard(){
tableView.endEditing(true)
}
override func viewDidAppear(animated: Bool) {
// Refresh the table to ensure any data changes are displayed
tableView.reloadData()
// Delegate the search bar to this table view class
searchBar.delegate = self
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
// Dismiss the keyboard
searchBar.resignFirstResponder()
// Force reload of table data
self.loadObjects()
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
// Dismiss the keyboard
searchBar.resignFirstResponder()
// Force reload of table data
self.loadObjects()
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
// Clear any search criteria
searchBar.text = ""
// Dismiss the keyboard
searchBar.resignFirstResponder()
// Force reload of table data
self.loadObjects()
}
}
With content mode set to Aspect Fill, try setting clips to bounds to true as well, reason being the content mode aspect fill keeps on filling the frame of the image view till the frame is fully filled with content also keeping the aspect ratio intact. In the process of filling the container with image maintaining aspect ratio, either vertical or horizontal frame is fully filled, and the filling is continued till the other (if horizontal than vertical or vise versa) portion is fully filled. Thus the first filled portion either across vertical or horizontal will go out of bounds and the content will be visible outside the frame of the image view. To clip the extra content we need to clip the extra portion using imageView's clipsToBounds property set to true
cell.photo.contentMode = UIViewContentMode.ScaleAspectFill
cell.photo.clipsToBounds = true
Reference: an answer in another post which gives you clear insight - https://stackoverflow.com/a/14220605/3021573
Alternatively for guys who prefer Interface Builder, you can check the Clip Subviews at your image view when you choose Aspect Fill, then it will not resize your Image View but still keep the same aspect ratio.
If you want to maintain the aspect ratio with varying width ,then use AspectFill and also use the imageview property clipstoBounds,it will not spread the imageview beyond the frame
From apple doc:
UIViewContentModeScaleToFill
Scales the content to fit the size of itself by changing the aspect
ratio of the content if necessary
UIViewContentModeScaleAspectFill
Scales the content to fill the size of the view. Some portion of the
content may be clipped to fill the view’s bounds.
So your options are to use
UIViewContentModeScaleToFill, and possibly change the aspect ratio of displayed image,
UIViewContentModeScaleAspectFill, and use clipToBounds = YES on your PFImageView, to clip portions of image out of bounds.