Swift - UICollectionView: How to auto-layout the label spacing in the Header - ios

I'm trying to create a custom calendar, I need to change the spacing of the header labels when the orientation is changed.
I'm using the following code to change the size and spacing between the cells, but how can I do the same for the labels in the header. The header is a custom class.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
updateCollectionViewLayout(with: size)
}
private func updateCollectionViewLayout(with size: CGSize) {
let itemSizeForPortraitMode : CGSize = CGSize(width: 40, height: 40)
let itemSizeForLandscapeMode: CGSize = CGSize(width: 40, height: 40)
var minimumItemSpacing: CGFloat
if let layout = calendarCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.itemSize = (size.width < size.height) ? itemSizeForPortraitMode : itemSizeForLandscapeMode
minimumItemSpacing = (size.width - (7 * 40)) / 6
layout.minimumInteritemSpacing = minimumItemSpacing
layout.invalidateLayout()
calendarCollectionView.updateConstraints()
}
}
Thanks for help.

I found the solution, I added a function to my header custom class:
override func layoutSubviews() {
super.layoutSubviews()
var labelFrame = CGRect(x: 0.0, y: 43, width: self.bounds.size.width / 7.0, height: 20)
for lbl in self.subviews {
if lbl.tag == 1 || lbl.tag == 2 || lbl.tag == 3 || lbl.tag == 4 || lbl.tag == 5 || lbl.tag == 6 || lbl.tag == 7 {
lbl.frame = labelFrame
labelFrame.origin.x += labelFrame.size.width
}
}
}

Related

UICollectionView does not adapt correctly on iPad rotate

I am trying to make an adaptive layout using a UICollectionView
On an iPhone I would like my view to stack, on an iPad I would like it to arrange 3 in a row.
This is working, however when I rotate my iPad and rotate it back again, my subviews do not return the position they were in originally at in that rotation.
You can see the behaviour if you pay attention to the Recognition item in the gif
This is my view controller
class AdaptiveLayoutCollectionView: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
registerCells()
configureLayout(with: view.bounds.size)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
configureLayout(with: size)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
configureLayout(with: view.bounds.size)
}
final func setLayout(as layout: CollectionViewLayoutOption) {
self.layout = layout
configureLayout(with: view.bounds.size)
}
func registerCells() { /* register cells in super view */ }
private func configureLayout(with size: CGSize) {
guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
flowLayout.minimumInteritemSpacing = 0
flowLayout.minimumLineSpacing = 0
flowLayout.sectionInset = UIEdgeInsets(top: 8.0, left: 0, bottom: 8.0, right: 0)
if traitCollection.horizontalSizeClass == .regular {
//let minItemWidth: CGFloat = 300
let minItemWidth: CGFloat = size.width / 3
let numberOfCell = size.width / minItemWidth
let width = floor((numberOfCell / floor(numberOfCell)) * minItemWidth)
flowLayout.itemSize = CGSize(width: width, height: 80)
} else {
flowLayout.itemSize = CGSize(width: size.width, height: 80)
}
collectionView.reloadData()
}
}

Correct remote video size on iPhoneX during video call using webrtc iOS swift

I am using webRTC for video calling. Everything is running smooth but
I am struggling with aspect ratio of Remote video on iPhoneX, XSMax. I am seeing lot of zoom in video. Can you please help me out how I can manage remote video on devices that have notch. Below is the code where I am handling remote size.
func videoView(_ videoView: RTCEAGLVideoView, didChangeVideoSize size: CGSize) {
print(size)
let defaultAspectRatio: CGSize = CGSize(width: 4, height: 3)
let aspectRatio: CGSize = size.equalTo(CGSize.zero) ? defaultAspectRatio : size
let videoRect: CGRect = self.view.bounds
let maxFloat = CGFloat.maximum(self.view.frame.width, self.view.frame.height)
let newAspectRatio = aspectRatio.width / aspectRatio.height
var frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
if (aspectRatio.width < aspectRatio.height) {
frame.size.width = maxFloat;
frame.size.height = frame.size.width / newAspectRatio;
} else {
frame.size.height = maxFloat;
frame.size.width = frame.size.height * newAspectRatio;
}
frame.origin.x = (self.view.frame.width - frame.size.width) / 2
frame.origin.y = (self.view.frame.height - frame.size.height) / 2
self.remoteView.frame = frame
}
According to #Eysner's answer, what is work for me, the final code (written using swift 5):
import UIKit
import WebRTC
final class WebRTCView: UIView, RTCVideoViewDelegate {
let videoView = RTCEAGLVideoView(frame: .zero)
var videoSize = CGSize.zero
override init(frame: CGRect) {
super.init(frame: frame)
videoView.delegate = self
addSubview(videoView)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
videoView.delegate = self
addSubview(videoView)
}
func videoView(_ videoView: RTCVideoRenderer, didChangeVideoSize size: CGSize) {
self.videoSize = size
setNeedsLayout()
}
override func layoutSubviews() {
super.layoutSubviews()
guard videoSize.width > 0 && videoSize.height > 0 else {
videoView.frame = bounds
return
}
var videoFrame = AVMakeRect(aspectRatio: videoSize, insideRect: bounds)
let scale = videoFrame.size.aspectFitScale(in: bounds.size)
videoFrame.size.width = videoFrame.size.width * CGFloat(scale)
videoFrame.size.height = videoFrame.size.height * CGFloat(scale)
videoView.frame = videoFrame
videoView.center = CGPoint(x: bounds.midX, y: bounds.midY)
}
}
extension CGSize {
func aspectFitScale(in container: CGSize) -> CGFloat {
if height <= container.height && width > container.width {
return container.width / width
}
if height > container.height && width > container.width {
return min(container.width / width, container.height / height)
}
if height > container.height && width <= container.width {
return container.height / height
}
if height <= container.height && width <= container.width {
return min(container.width / width, container.height / height)
}
return 1.0
}
}
There is aspectFitScale function, it is simply to describe logic, you can refactor it if you want to.
I'm using this approach in the demo:
https://github.com/Maxatma/Walkie-Talkie/
You can make some class with show video -
class SomeVideoView: UIView, RTCVideoViewDelegate {
let videoView = RTCEAGLVideoView(frame: .zero)
var videoSize = CGSize.zero
With init method
override init(frame: CGRect) {
super.init(frame: frame)
videoView.delegate = self
self.addSubview(videoView)
...
Handle delegate method videoView:didChangeVideoSize like this (we don't need change frame in UI, only mark needsLayout)
func videoView(_ videoView: RTCEAGLVideoView, didChangeVideoSize size: CGSize) {
if (self.videoView == videoView) {
self.videoSize = size
}
self.setNeedsLayout()
}
And overwrite layoutSubviews method
override func layoutSubviews() {
if (self.videoSize.width > 0 && self.videoSize.height > 0) {
var videoFrame = AVMakeRect(aspectRatio: self.videoSize, insideRect: bounds)
var scale = 1.0
if (videoFrame.size.width > videoFrame.size.height) {
scale = bounds.size.height / videoFrame.size.height
} else {
scale = bounds.size.width / videoFrame.size.width
}
videoFrame.size.width = videoFrame.size.width * scale
videoFrame.size.height = videoFrame.size.height * scale
self.videoView.frame = videoFrame
self.videoView.center = CGPointMake(bounds.midX, bounds.midY)
} else {
self.videoView.frame = bounds
}
...
}
And set up SomeVideoView is full screen (this link can be help with it)
Safe Area of Xcode 9
just simple using AVMakeRect
//TODO: Default mobile
if size.width < size.height{
newSize = aspectFill(aspectRatio: size, minimumSize: UIScreen.main.bounds.size)
}else{
//Default computer
newSize = AVMakeRect(aspectRatio: size, insideRect: UIScreen.main.bounds).size
}
for sometimes user may have call from website, so i didnt make fill from it
func aspectFill(aspectRatio : CGSize, minimumSize: CGSize) -> CGSize {
let mW = minimumSize.width / aspectRatio.width;
let mH = minimumSize.height / aspectRatio.height;
var temp = minimumSize
if( mH > mW ) {
temp.width = minimumSize.height / aspectRatio.height * aspectRatio.width;
}
else if( mW > mH ) {
temp.height = minimumSize.width / aspectRatio.width * aspectRatio.height;
}
return temp;
}

UICollection view cell with large text problem

I have a cell with description(UILabel) and two buttons after the description I use this to get the description text height
let constraintRect = CGSize(width: maxWidth, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedStringKey.font: font], context: nil)
return ceil(boundingBox.height)
but the problem is when I do any action on the buttons with a huge description cell the actions is not trigged this is a ver annoying a problem and tried a lot of solutions but nothing works any help ?
this is where I set the height for cells
override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
if self.items.count < 1 {
return CGSize(width: (self.view.bounds.width - 16), height: 200)
}
if indexPath.item < self.items.count {
let item = self.items[indexPath.item]
var h = CGFloat(0)
h = item["description"].stringValue.height(with: (self.collectionView.bounds.width - 44), font: UIFont(name: "Helvetica", size: self.contentLabelFontSize)!)
self.descriptionHeight = h
if item["images"].isEmpty == false {
if item["images"][0]["original"].stringValue != "" {
let hi = 300
let im_h = CGFloat(hi)
h += im_h
}
}
if self.type == "events" {
h += 260
}
else if item["type"].stringValue == "event" {
h += 250
}
else {
h += 170
}
let size = CGSize(width: (self.view.bounds.width - 16), height: h)
return size
}
return CGSize(width: (self.view.bounds.width - 16), height: 50)
}
And this is where is set the height of label at the PostCell class :
self.contentLabel.frame = CGRect(x: 10, y: 110, width: (self.bounds.width - 20), height: (self.bounds.height - self.imageHeight - 170))

iOS - CollectionView rotation responds slow

I have a collectionView where I am using the following to manage rotation of the cells:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
DispatchQueue.main.async {
coordinator.animate(alongsideTransition: nil, completion: { context in
self.collectionView?.collectionViewLayout.invalidateLayout()
self.collectionView?.collectionViewLayout.finalizeCollectionViewUpdates()
})
}
}
I see that the size change on rotation is very slow ( takes ~ 3- 4 seconds ). Is there a better way to get smoother rotation transition? I have dynamic cell sizing from sizeForItemAtIndexPath, code is :
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var width = CGFloat()
if traitCollection.userInterfaceIdiom == .phone {
width = collectionView.frame.size.width - 2.0 * margin
}
else if traitCollection.userInterfaceIdiom == .pad {
if UIDevice.current.orientation.isLandscape {
width = floor(collectionView.frame.size.width - 14.0 * margin)
}
else {
width = floor(collectionView.frame.size.width - 6.0 * margin)
}
}
let post = self.postListForLoggedInUserFromRealm?.posts[(indexPath as NSIndexPath).item]
if let description = post?.description {
let rect = NSString(string: description).boundingRect(with: CGSize(width: view.frame.width * 0.8, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [.font: UIFont(name: "Avenir", size: CGFloat(titleFont))!], context: nil)
var knownHeight : CGFloat = 64
if(post?.imageURL != nil && post?.imageURL != "") {
guard let imageURL = NSURL(string: (post?.imageURL!)!) else { return CGSize(width: width, height: rect.height + knownHeight )}
if let imageSource = CGImageSourceCreateWithURL((imageURL as CFURL), nil) {
if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary? {
let pixelHeight = imageProperties[kCGImagePropertyPixelHeight] as! CGFloat
let imageMaxHeight: CGFloat = (CIConstants.DeviceType.DeviceTypePhone == deviceType ) ? 400 : 700
let imageHeight = pixelHeight > imageMaxHeight ? imageMaxHeight : pixelHeight
knownHeight = knownHeight + CGFloat(imageHeight)
return CGSize(width: width, height: rect.height + knownHeight)
}
}
}
return CGSize(width: width, height: rect.height + knownHeight)
}
else if(post?.imageURL != nil && post?.imageURL != "") {
var knownHeight : CGFloat = 10 + 44 + 8 + 44 + 10
guard let imageURL = NSURL(string: (post?.imageURL!)!) else { return CGSize(width: width, height: knownHeight + 70)}
if let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, nil) {
if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary? {
let pixelHeight = imageProperties[kCGImagePropertyPixelHeight] as! CGFloat
let imageMaxHeight: CGFloat = (CIConstants.DeviceType.DeviceTypePhone == deviceType ) ? 400 : 700
let imageHeight = imageMaxHeight > 700 ? imageMaxHeight : pixelHeight
knownHeight = knownHeight + CGFloat(imageHeight)
}
}
return CGSize(width: width, height: knownHeight)
}
return CGSize(width: 850, height: 70)
}
Any suggestions would be truly appreciated! Thanks for your time :)
EDIT: When I use this code, the delay is gone but I lose the margins ( on left and right of collectionView) in Portrait mode.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
DispatchQueue.main.async {
coordinator.animate(alongsideTransition: { context in
self.collectionView?.collectionViewLayout.invalidateLayout()
self.collectionView?.collectionViewLayout.finalizeCollectionViewUpdates()
})
}
super.viewWillTransition(to: size, with: coordinator)
}

Set height for popover in landscape mode

I want to modify the height of popover in landscape mode, but it only work in portrait mode.
I want it's height equal to screenSize.height * 0.7, but it doesn't work with my code below.
Here is my code:
if let orientation = UIDevice.current.value(forKey: "orientation") as? Int {
let diamondViewController = DiamondViewController()
diamondViewController.mode = .buyDiamondPopup
diamondViewController.resetBackgroundColor = {
self.view.backgroundColor = .clear
}
let screenSize = UIScreen.main.bounds
if orientation == 3 { // LandscapeRight
diamondViewController.preferredContentSize = CGSize(width: screenSize.width * 0.6, height:
screenSize.height * 0.7)
} else {
diamondViewController.preferredContentSize = CGSize(width: screenSize.width - 60, height:
min(screenSize.height - 180, CGFloat(5 * 70 + 110) ))
}
diamondViewController.modalPresentationStyle = .popover
if let popover = diamondViewController.popoverPresentationController {
popover.permittedArrowDirections = .init(rawValue: 0)
popover.sourceView = self.view
popover.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popover.delegate = self
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
self.present(diamondViewController, animated: true, completion: nil)
}
}
...
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
Instead of this
diamondViewController.preferredContentSize = CGSize(width: screenSize.width * 0.6, height: screenSize.height * 0.7)
Try this below
diamondViewController.view = CGRect(x: diamondViewController.view.frame.origin.x ,y: diamondViewController.view.frame.origin.y ,width: screenSize.width * 0.6, height: screenSize.height * 0.7)
If it doesn't help then let me know.
Have you tried using the "Vary for traits" tool? It allows you to apply different constraints depending on the devices orientation. It can be found near the bottom right corner in storyboard next to the different constraint options.
I think you have to set landscape's height in a dispatched thread. Here is my suggestion.
if let orientation = UIDevice.current.value(forKey: "orientation") as? Int {
let diamondViewController = DiamondViewController()
diamondViewController.mode = .buyDiamondPopup
diamondViewController.resetBackgroundColor = {
self.view.backgroundColor = .clear
}
// HERE IS THE CHANGE: CALL FUNC TO GET SIZE
let size = diamondViewController.myPreferedContentSize(landscape: landscape)
diamondViewController.preferredContentSize = size
...
}
}
The func myPreferedContentSize is defined in 'diamondViewController' class as following:
func myPreferedConentSize(landcape: Bool) -> CGSize
{
let screenSize = UIScreen.main.bounds
let retSize: CGSize
if landscape { // LandscapeRight
retSize = CGSize(width: screenSize.width * 0.6, height:
screenSize.height * 0.7)
} else {
retSize = CGSize(width: screenSize.width - 60, height:
min(screenSize.height - 180, CGFloat(5 * 70 + 110) ))
}
return retSize
}
In your diamondViewController class, override this func and add codes:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let landscape = size.width > size.height
DispatchQueue.main.async {
[weak self] in
guard let this = self else { return }
this.preferredContentSize = this.myPreferedConentSize(
landscape: landscape)
}
}
It is important to call func myPreferedConentSize in DispatchQueue.main.async thread. In this async main thread, it will ask your view controller to set preferred content size in either portrait or landscape orientation correctly.

Resources