I have a UIScrollView in an UIViewController that is presented as Popover. On iPads the contentSize of the ScrollView is not calculated correctly as the view size is always the same as UIScreen.main.bounds.width. As the popover is smaller as the screen the paging fails as each slide is wider than the width of the popover width.
Here is how I load the popover:
let storyboard = UIStoryboard(name: "Onboarding", bundle: nil)
let onboardingVC = storyboard.instantiateViewController(identifier: "Onboarding")
if let popoverController = onboardingVC.popoverPresentationController {
popoverController.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 2, width: 0, height: 0)
popoverController.sourceView = self.view
popoverController.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
}
present(onboardingVC, animated: true, completion: nil)
And this is how I calculate the contentSize in the OnboardingViewController:
func setupSlideScrollView(slides : [Slide]) {
scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(slides.count), height: 1.0)
scrollView.isPagingEnabled = true
for i in 0 ..< slides.count {
slides[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
scrollView.addSubview(slides[i])
}
}
SOLUTION:
I think I found the solution myself. I had to call func setupSlideScrollView in override func viewWillLayoutSubviews() and not in override func viewDidLoad().
Related
I am following a tutorial to create a swift app that adds a number of views to a scroll view and then allows me to scroll between them. I have the app working and understand it for the most part. When I change the orientation of the device the views width don't get updated so I have parts of more than one view controller on the screen? Does anybody know how to fix this? From my understanding I need to call something in the viewsDidTransition method to redraw the views. Any help would be greatly appreciated. Thanks!
Here is what I have so far:
import UIKit
class ViewController: UIViewController {
private let scrollView = UIScrollView()
private let pageControl: UIPageControl = {
let pageControl = UIPageControl()
pageControl.numberOfPages = 5
pageControl.backgroundColor = .systemBlue
return pageControl
}()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
pageControl.addTarget(self,
action: #selector(pageControlDidChange(_:)),
for: .valueChanged)
scrollView.backgroundColor = .red
view.addSubview(scrollView)
view.addSubview(pageControl)
}
#objc private func pageControlDidChange(_ sender: UIPageControl){
let current = sender.currentPage
scrollView.setContentOffset(CGPoint(x: CGFloat(current) * view.frame.size.width,
y: 0), animated: true)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
pageControl.frame = CGRect(x: 10, y: view.frame.size.height - 100, width: view.frame.size.width - 20, height: 70)
scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height - 100)
if scrollView.subviews.count == 2 {
configureScrollView()
}
}
private func configureScrollView(){
scrollView.contentSize = CGSize(width: view.frame.size.width*5, height: scrollView.frame.size.height)
scrollView.isPagingEnabled = true
let colors: [UIColor] = [.systemRed, .systemGray, .systemGreen, .systemOrange, .systemPurple]
for x in 0..<5{
let page = UIView(frame: CGRect(x: CGFloat(x) * view.frame.size.width, y: 0, width: view.frame.size.width, height: scrollView.frame.size.height))
page.backgroundColor = colors[x]
scrollView.addSubview(page)
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
print("hello world")
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
pageControl.currentPage = Int(floorf(Float(scrollView.contentOffset.x) / Float(scrollView.frame.size.width)))
}
}
Trying to add four UIView's in UIScrollView
The issue is next (2, 3, and 4) view gets a bit off from the visible screen view when scroll.
override init(frame: CGRect) {
super.init(frame: frame)
configureSlider()
}
private func configureSlider() {
Bundle.main.loadNibNamed("CarouselSliderView", owner: self, options: nil)
addSubview(contentView)
for i in 0...3 {
let view = UIView()
switch i {
case 0:
view.backgroundColor = .brown
case 1:
view.backgroundColor = .yellow
case 2:
view.backgroundColor = .green
case 3:
view.backgroundColor = .blue
default:
view.backgroundColor = .red
}
slides.append(view)
}
setupSlideScrollView(slides: slides)
pageControl.numberOfPages = slides.count
pageControl.currentPage = 0
self.bringSubviewToFront(pageControl)
}
set each UIView frames:
private func setupSlideScrollView(slides : [UIView]) {
let screenRect = UIScreen.main.bounds
let screenWidth = screenRect.size.width
for i in 0 ..< slides.count {
slides[i].frame = CGRect(x: screenWidth * CGFloat(i), y: 0, width: screenWidth, height: self.frame.height)
topScrollView.addSubview(slides[i])
}
topScrollView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: self.frame.height)
topScrollView.contentSize = CGSize(width: screenWidth * CGFloat(slides.count), height: self.frame.height)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x/self.frame.width)
pageControl.currentPage = Int(pageIndex)
}
I have used the iCarousel Framework in my View Controller. But, I am not sure how can I add a label below each image while scrolling in the carousel view. Would appreciate your help! Thanks!
func numberOfItems(in carousel: iCarousel) -> Int {
return 10
}
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
let view = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width/1.4, height: 300))
view.backgroundColor = .red
let imageview = UIImageView(frame: view.bounds)
view.addSubview(imageview)
imageview.contentMode = .scaleToFill
imageview.image = UIImage(named: "Dog_\(index+1)")
return view
}
func carouselDidEndScrollingAnimation() {
if (myCarousel.currentItemIndex == 1) {
dogNameLabel.text = "Brownie!"
}
}
let myCarousel: iCarousel = {
let view = iCarousel()
view.type = .rotary
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myCarousel)
myCarousel.dataSource = self
myCarousel.frame = CGRect(x: 0, y: 220, width: view.frame.size.width, height: 400)
}
There are too many option. You can create custom view (xib) for reusability. You need to label in contentView above imageView.
But you need to create view like that.
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
let view = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width/1.4, height: 300))
view.backgroundColor = .red
let imageview = UIImageView(frame: view.bounds)
view.addSubview(imageview)
imageview.contentMode = .scaleToFill
imageview.image = UIImage(named: "Dog_\(index+1)")
let label = UILabel()
label.text = "Enter your text"
label.frame = CGRect(x: 0, y: 20, width: view.frame.size.width, height: 30)
//or use constraints
view.addSubview(label)
return view
}
In my viewController which is the delegate of my scrollView
override func viewWillAppear(_ animated: Bool) {
slides = loadSlides()
setupSlideScrollView(slides: slides)
pageControl.numberOfPages = slides.count
pageControl.currentPage = 0
view.bringSubview(toFront: pageControl)
textField.becomeFirstResponder()
}
func loadSlides() -> [Slide] {
let slide1: Slide = Bundle.main.loadNibNamed("Slide", owner: self, options: nil)?.first as! Slide
slide1.quizLabel.text = "This is slide1"
slide1.hintLabel.text = "yeah"
let slide2: Slide = Bundle.main.loadNibNamed("Slide", owner: self, options: nil)?.first as! Slide
slide2.quizLabel.text = "This is slide2"
slide2.hintLabel.text = "YEAH"
let slides = [slide1, slide2]
return slides
}
func setupSlideScrollView(slides : [Slide]) {
let width = view.frame.width
let height = view.frame.height
scrollView.frame = CGRect(x: 0,
y: 0,
width: width,
height: height)
scrollView.contentSize = CGSize(width: CGFloat(slides.count) * width, height: height)
scrollView.isPagingEnabled = true
for slide in slides {
slide.quizLabel.textColor = UIColor.white
slide.quizLabel.font = UIFont(name: (textField.font?.fontName)!, size: 20)
slide.hintLabel.textColor = UIColor.lightGray
slide.hintLabel.font = UIFont(name: (textField.font?.fontName)!, size: 18)
slide.frame = CGRect(x: CGFloat(slides.index(of: slide)!) * width,
y: 0,
width: width,
height: height)
scrollView.addSubview(slide)
}
}
When running, the pageControl shows up with a sliding animation. It seems it slides from the origin of the view. I have no idea why it comes out like that.
The pageControl is at the top
My segue settings as followed
p.s. Also, anyway to solve the similar problem for the sliding labels and the cursor?
I would like to appreciate your help in advance!
How can I trim this function down to be as lean as possible? The only difference between the if and else blocks is the height of the UiImageView--if true, height is 300, else 150. I can't seem to figure out the best way to refactor this function without either redundant code (like in the example) or with a really long function that just seems way too long for something that seems rather simple.
I left the example crude to make the intention clear.
class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
banner(profileHasCustomBanner: false)
}
// banner
func banner(profileHasCustomBanner: Bool) {
if profileHasCustomBanner == true {
let bannerImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 300))
bannerImageView.image = #imageLiteral(resourceName: "the-banner")
view.contentMode = .scaleAspectFit
view.addSubview(bannerImageView)
} else {
let bannerImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 150))
bannerImageView.image = #imageLiteral(resourceName: "the-banner")
view.contentMode = .scaleAspectFit
view.addSubview(bannerImageView)
}
}
}
Define a height variable based on the condition.
func banner(profileHasCustomBanner: Bool) {
let height: CGFloat = profileHasCustomerBanner ? 300 : 150
let bannerImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: height))
bannerImageView.image = #imageLiteral(resourceName: "the-banner")
view.contentMode = .scaleAspectFit
view.addSubview(bannerImageView)
}