I've been successfully using the function below to take snapshots of UIViews, however, now that I'm using it inside a UITableViewCell, it no longer works.
static func pictureTaker(_ rawView: UIView) -> UIImageView {
UIGraphicsBeginImageContextWithOptions(rawView.bounds.size, false, 0)
rawView.drawHierarchy(in: rawView.bounds, afterScreenUpdates: true)
let screenshot = UIGraphicsGetImageFromCurrentImageContext();
let viewImage = UIImageView(image: screenshot)
UIGraphicsEndImageContext();
viewImage.frame = rawView.frame
return viewImage
}
Here is a trimmed down version of the function I'm using to populate the UITableViewCell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = gamesHolder.dequeueReusableCell(withIdentifier: textCellIdentifier, for: indexPath) as! StandardGameCell
let row = indexPath.row
let boardPreviewTemplate = UIView(frame: CGRect(x: 0, y: 0, width: cell.previewBoard.frame.width, height: cell.previewBoard.frame.height))
GameManagement.setupBoard(boardPreviewTemplate, boardItems: gamesArray[row].board)
let gamePicture = PageManagement.pictureTaker(boardPreviewTemplate)
gamePicture.frame = CGRect(x: 0, y: 0, width: gamePicture.frame.width, height: gamePicture.frame.height)
//Removing preview board items and replacing with picture
cell.previewBoard.subviews.forEach({ $0.removeFromSuperview() })
cell.previewBoard.addSubview(gamePicture)
return cell
}
In the above tableView function, I'm programmatically creating a new view to replicate cell.previewBoard, but I also tried directly snapshotting cell.previewBoard and that didn't work either. I think the issue has something to do with the timing of the UITableViewCell loading.
I'm also 100% positive that the function is returning an image (but a blank one) and that it's where I'm intending it to be. If I change UIGraphicsBeginImageContextWithOptions(rawView.bounds.size, false, 0) to UIGraphicsBeginImageContextWithOptions(rawView.bounds.size, true, 0), the returned image shows up as entirely black.
As one final note, I discovered that boardPreviewPicture.snapshotView(afterScreenUpdates: true) would return an image, but it made scrolling through the UITableView very slow. Maybe there is another workaround where I can use snapshotView() instead.
I ran into the same problem. What I noticed is that when UIGraphicsBeginImageContextWithOptions is called inside tableView(_:cellForRowAt:), the result from UIGraphicsGetImageFromCurrentImageContext() is blank. Take note that in the documentation it is stated that UIGraphicsGetImageFromCurrentImageContext:
This function may be called from any thread of your app.
However, the solution that I discovered is to call it inside the main thread. In your case, maybe you can do something like:
DispatchQueue.main.async {
let gamePicture = PageManagement.pictureTaker(boardPreviewTemplate)
gamePicture.frame = CGRect(x: 0, y: 0, width: gamePicture.frame.width, height: gamePicture.frame.height)
//Removing preview board items and replacing with picture
cell.previewBoard.subviews.forEach({ $0.removeFromSuperview() })
cell.previewBoard.addSubview(gamePicture)
}
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 have some configuration based on the type of data I receive ( the insets to add in the cell.configure(with item: item) {}, however the cells are not re-used properly. I am using RxSwift for binding, though I don't see the issue in there.
tried setting image.layoutMargins = UIEdgeInsets.zero in prepareForReuse(), in the cell.configure(with: item) etc. Doesn't seem to work.
brandsGroup
.bind(to: rx.items(cellIdentifier: StudioCellConstants.brandReuseIdentifier,
cellType: BrandCollectionViewXibCell.self)) { (_, item, cell) in
cell.configure(with: item)
}.disposed(by: disposeBag)
override func prepareForReuse() {
super.prepareForReuse()
image.layoutMargins = UIEdgeInsets.zero
}
Update:
Eventually I ended up setting back the frame like this, but it feels like a hacky solution.
A better way would be much appreciated.
image.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: imageContainerView.frame.width, height: imageContainerView.frame.height)
Are you setting the content fill mode of imageview in cell correctly.
//Swift code(I am not familier in rxswift but the concept should be the same)
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
I prefered scaleAspectFill here as I assume u need the image to cover the whole imageView
I got a ViewController, with two UITableView in it.
They both get data from the same object, but from different arrays inside that object.
My second Tableview doesn't show me all the items, only 5.
The TableViews are both dynamic and size themself.
Here is my cellForRowAt function:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == tableViewSteps, //This is the table that doesn't show right
let cell = tableView.dequeueReusableCell(withIdentifier: "stepsCell", for: indexPath) as? StepsTableViewCell {
let text = detailItem?.itemStepAr[indexPath.row] as! String
cell.textAreaOutlet.text = "Schritt \(indexPath.row + 1) \n\(text)"
let textAsNSString = cell.textAreaOutlet.text as NSString
let lineBreakRange = textAsNSString.range(of: "\n")
let newAttributedText = NSMutableAttributedString(attributedString: cell.textAreaOutlet.attributedText)
let boldRange: NSRange
if lineBreakRange.location < textAsNSString.length {
boldRange = NSRange(location: 0, length: lineBreakRange.location)
} else {
boldRange = NSRange(location: 0, length: textAsNSString.length)
}
newAttributedText.addAttribute(NSAttributedString.Key.font, value: UIFont.preferredFont(forTextStyle: UIFont.TextStyle.headline), range: boldRange)
cell.textAreaOutlet.attributedText = newAttributedText
let fixedWidth = cell.textAreaOutlet.frame.size.width
let newSize = cell.textAreaOutlet.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
cell.textAreaOutlet.frame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
cell.textAreaOutlet.isScrollEnabled = false
tableStepsHeight = tableStepsHeight + Int(newSize.height)
cell.textAreaOutlet.textColor = UIColor.lightGray
tableViewSteps.frame = CGRect(x:0, y: currentViewHeight + 20, width: 375, height: tableStepsHeight)
changeScrollHeight()
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "ingredsCell")! //1.
let text = detailItem?.itemIngredAr[indexPath.row]
cell.textLabel?.text = text as? String
cell.textLabel?.font = UIFont(name: "Futura-Medium", size: 17)
cell.textLabel?.textColor = UIColor.lightGray
return cell
}
}
as commented, the first tableview is the one, that doesn't work properly.
It only runs 5 times, when I watch it with the debugger, but there are more Items in the Array.
Edit:
Here is my numberOfRowsInSection function
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (tableView == tableViewSteps) {
return detailItem?.itemStepAr.count ?? 0
}
else {
return detailItem?.itemIngredAr.count ?? 0
}
}
Edit 2:
When I enable scrolling in my TableView and then try to scroll, all the items appear. But the scrolling should be disabled, because it automatic resizes (which works)
Final edit:
I added like some more height to the tableViewand enabled scroll (but disabled the bounce, so u don't see anything from the scroll)
Now it loads properly and everything works fine
A UITableView works with recycling UITableViewCells. This means that a tableview will only load the visible cells first, and only when you scroll, will it load more cells (by recycling the old cells).
The issue I can see is that you may try to get the height of the tableview, to update a height constraint (so the tableview shows in its entirety). But that may be an incorrect calculation (as said before, it only instantiates the first x cells, so it can't know the height of the remaining cells). Also, this calculation should never be performed from cellForRowAt, as that method should never do things outside the cell (unless you want to know when a cell has become visible).
You can fix this by making your cells all have a predefined height, and then multiplying that by the number of cells.
If you have self-sizing cells, it can't be done (read: it can be done, but it's too unwieldy and inefficient to be practical).
You could try to look into taking all other views that should scroll above and below the tableview, into tableviewcells. Then you can just use a regular tableview, no fancy height constraints and calculations needed.
I have a problem while scrolling my collectionView
after choosing a cell, the last cell is not available for choosing
func changeViewSize(from: CGFloat, to: CGFloat, indexPath: IndexPath) {
UIView.transition(with: myCollection, duration: 0.5, options: .beginFromCurrentState, animations: {() -> Void in
let cell = self.myCollection.cellForItem(at: indexPath)!
let cellCenter = CGPoint(x: cell.center.x, y: cell.center.y + 50)
if cell.bounds.height == from {
let sizee = CGRect(x: cell.center.x, y: cell.center.y, width: cell.frame.width, height: to)
self.myCollection.cellForItem(at: indexPath)?.frame = sizee
self.myCollection.cellForItem(at: indexPath)?.center = cellCenter
for x in self.indexPathss {
if x.row > indexPath.row {
print("this is cells")
print(self.myCollection.visibleCells)
let cell = self.myCollection.cellForItem(at: x)!
cell.center.y += 100
}
}
}
}, completion: {(_ finished: Bool) -> Void in
print("finished animating of cell!!!")
})
}
It looks like you're changing cell sizes and positions manually. The UICollectionView won't be aware of these changes and therefore its size is not changing. The last cells simply move out of the visible area of the collection view.
I haven't done something like this before, but have a look at performBatchUpdates(_:completion:). I'd try to reload the cell that you want to make bigger using that method. Your layout will have to supply the correct attributes, i.e. size.
From the screenshots it looks like maybe you could use a UITableView instead? If that's a possibility it might simplify things. It also has a performBatchUpdates(_:completion:) method, and the UITableViewDelegate would then have to provide the correct heights for the rows.
Because of spacing of cell we used collectionView and I think changing height of tableviewcell is easier than collectionviewcell if you have an idea for spacing of tablviewcell wi will be glad to have your idea.
I implemented a table view, but the scrolling is lagging. Why is it lagging? I am reusing the cell, but it is still lagging. When I scroll, the memory spikes and comes back to normal when the scrolling stops. If it is of any use, I implemented a collectionview inside of the table view cell.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: BoxCell = table.dequeueReusableCellWithIdentifier("CELL", forIndexPath: indexPath) as! BoxCell
cell.pic.image = pic[indexPath.row].circle
return cell
}
extension UIImage {
var circle: UIImage? {
let square = CGSize(width: min(size.width, size.height), height: min(size.width, size.height))
let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: square))
imageView.contentMode = .ScaleAspectFill
imageView.image = self
imageView.layer.cornerRadius = square.width/2
imageView.layer.masksToBounds = true
UIGraphicsBeginImageContext(imageView.bounds.size)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
imageView.layer.renderInContext(context)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result
}
}
How big are your images? If they are big images table will slowdown. I have recently encountered with that problem. Using smaller images make it faster again.
A lot is happening in this cell maybe tone down its resources. For example the collection view is being reloaded every time you are scrolling through the table view. "cell.collectionview.reloadData()". Maybe take the contents of the cell and load it once in an Awake From Nib custom cell instead of calling all these operators, image and coordinates for every cell each time you are scrolling through your table view. The memory is spiking because you are also reloading your collection view, no matter how large it is, every time you are scrolling. Everything is being handled on the main thread as well. Maybe move some image layout code to a background thread for a smoother scrolling action.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//Image work here (including image if statement)
})
Ideally move the image loading to awake from nib so it load once.