UIScrollView with paging enable not working properly - swift

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)
}

Related

How to update ScrollView Width after device rotation?

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)))
}
}

stackView content (SF-Symbols) distorted

I want to replicate the Apple Map App Buttons as seen here:
This is the code so far:
class CustomView: UIImageView {
init(frame: CGRect, corners: CACornerMask, systemName: String) {
super.init(frame: frame)
self.createBorders(corners: corners)
self.createImage(systemName: systemName)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createBorders(corners: CACornerMask) {
self.contentMode = .scaleAspectFill
self.clipsToBounds = false
self.layer.cornerRadius = 20
self.layer.maskedCorners = corners
self.layer.masksToBounds = false
self.layer.shadowOffset = .zero
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowRadius = 20
self.layer.shadowOpacity = 0.2
self.backgroundColor = .white
let shadowAmount: CGFloat = 2
let rect = CGRect(x: 0, y: 2, width: self.bounds.width + shadowAmount * 0.1, height: self.bounds.height + shadowAmount * 0.1)
self.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
func createImage(systemName: String) {
let image = UIImage(systemName: systemName)!
let renderer = UIGraphicsImageRenderer(bounds: self.frame)
let renderedImage = renderer.image { (_) in
image.draw(in: frame.insetBy(dx: 30, dy: 30))
}
And use it like this:
let size = CGSize(width: 100, height: 100)
let frame = CGRect(origin: CGPoint(x: 100, y: 100), size: size)
let infoView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], systemName: "info.circle")
let locationView = CustomView(frame: frame, corners: [], systemName: "location")
let twoDView = CustomView(frame: frame, corners: [.layerMinXMaxYCorner, .layerMaxXMaxYCorner], systemName: "view.2d")
let binocularsView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner], systemName: "binoculars.fill")
let stackView = UIStackView(arrangedSubviews: [infoView, locationView, twoDView, binocularsView])
stackView.frame = CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 100, height: 400))
stackView.distribution = .fillEqually
stackView.axis = .vertical
stackView.setCustomSpacing(20, after: twoDView)
view.addSubview(stackView)
Which makes it look like this:
AND: There aren't the thin lines between each Icon of the stackView :/
Thanks for your help!
Not sure what's going on, because when I ran you code as-is I did not get the stretched images you've shown.
To get "thin lines between each Icon", you could either modify your custom image view to add the lines to the middle icon image, or, what might be easier, would be to use a 1-point height UIView between each icon.
Also, you'll be better off using auto-layout.
See if this gives you the desired result:
class CustomView: UIImageView {
init(frame: CGRect, corners: CACornerMask, systemName: String) {
super.init(frame: frame)
self.createBorders(corners: corners)
self.createImage(systemName: systemName)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createBorders(corners: CACornerMask) {
self.contentMode = .scaleAspectFill
self.clipsToBounds = false
self.layer.cornerRadius = 20
self.layer.maskedCorners = corners
self.layer.masksToBounds = false
self.layer.shadowOffset = .zero
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowRadius = 20
self.layer.shadowOpacity = 0.2
self.backgroundColor = .white
let shadowAmount: CGFloat = 2
let rect = CGRect(x: 0, y: 2, width: self.bounds.width + shadowAmount * 0.1, height: self.bounds.height + shadowAmount * 0.1)
self.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
func createImage(systemName: String) {
guard let image = UIImage(systemName: systemName)?.withTintColor(.blue, renderingMode: .alwaysOriginal) else { return }
let renderer = UIGraphicsImageRenderer(bounds: self.frame)
let renderedImage = renderer.image { (_) in
image.draw(in: frame.insetBy(dx: 30, dy: 30))
}
self.image = renderedImage
}
}
class TestCustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
let size = CGSize(width: 100, height: 100)
let frame = CGRect(origin: .zero, size: size)
let infoView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], systemName: "info.circle")
let locationView = CustomView(frame: frame, corners: [], systemName: "location")
let twoDView = CustomView(frame: frame, corners: [.layerMinXMaxYCorner, .layerMaxXMaxYCorner], systemName: "view.2d")
let binocularsView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner], systemName: "binoculars.fill")
// create 2 separator views
let sep1 = UIView()
sep1.backgroundColor = .blue
let sep2 = UIView()
sep2.backgroundColor = .blue
let stackView = UIStackView(arrangedSubviews: [infoView, sep1, locationView, sep2, twoDView, binocularsView])
// use .fill, not .fillEqually
stackView.distribution = .fill
stackView.axis = .vertical
stackView.setCustomSpacing(20, after: twoDView)
view.addSubview(stackView)
// let's use auto-layout
stackView.translatesAutoresizingMaskIntoConstraints = false
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// only need top and leading constraints for the stack view
stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 100.0),
stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 100.0),
// both separator views need height constraint of 1.0
sep1.heightAnchor.constraint(equalToConstant: 1.0),
sep2.heightAnchor.constraint(equalToConstant: 1.0),
])
// set all images to the same 1:1 ratio size
[infoView, locationView, twoDView, binocularsView].forEach { v in
v.widthAnchor.constraint(equalToConstant: size.width).isActive = true
v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
}
}
}
This is what I get with that code example:

Resize Image in stackView, keep aspect-ratio

I want to clone the Apple Maps App UIButtons, they look like this:
This is my Code:
class CustomView: UIImageView {
init(frame: CGRect, corners: CACornerMask, systemName: String) {
super.init(frame: frame)
self.createBorders(corners: corners)
self.createImage(systemName: systemName)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createBorders(corners: CACornerMask) {
self.contentMode = .scaleAspectFill
self.clipsToBounds = false
self.layer.cornerRadius = 10
self.layer.maskedCorners = corners
self.layer.masksToBounds = false
self.layer.shadowOffset = .zero
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowRadius = 10
self.layer.shadowOpacity = 0.2
self.backgroundColor = .white
let shadowAmount: CGFloat = 2
let rect = CGRect(x: 0, y: 2, width: self.bounds.width + shadowAmount * 0.1, height: self.bounds.height + shadowAmount * 0.1)
self.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
func createImage(systemName: String) {
let image = UIImage(systemName: systemName)!
// let renderer = UIGraphicsImageRenderer(bounds: self.frame)
// let renderedImage = renderer.image { (_) in
// image.draw(in: frame.insetBy(dx: 16, dy: 30))
// }
// self.image = renderedImage.withRenderingMode(.alwaysTemplate)
self.image = image
}
}
And used it like this:
let size = CGSize(width: 10, height: 10)
let frame = CGRect(origin: CGPoint(x: 360, y: 45), size: size)
let infoView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], systemName: "info.circle")
let locationView = CustomView(frame: frame, corners: [], systemName: "location")
let plusView = CustomView(frame: frame, corners: [.layerMinXMaxYCorner, .layerMaxXMaxYCorner], systemName: "plus.circle")
let binocularsView = CustomView(frame: frame, corners: [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner], systemName: "binoculars.fill")
let stackView = UIStackView(arrangedSubviews: [infoView, locationView, plusView, binocularsView])
stackView.frame = CGRect(origin: CGPoint(x: 360, y: 45), size: CGSize(width: 45, height: 180))
stackView.distribution = .fillEqually
stackView.axis = .vertical
stackView.setCustomSpacing(3, after: plusView)
view.addSubview(stackView)
With this code, it looks like this:
As you can see, the icons are too big... So this is what I tried to shrink them: (uncommented the lines in createImage())
func createImage(systemName: String) {
let image = UIImage(systemName: systemName)!
let renderer = UIGraphicsImageRenderer(bounds: self.frame)
let renderedImage = renderer.image { (_) in
image.draw(in: frame.insetBy(dx: 16, dy: 30))
}
self.image = renderedImage.withRenderingMode(.alwaysTemplate)
}
But then its distorted...
Does can help me? :)
Thank you!!!!
Rather than subclassing UIImageView subclass UIView and add a UIImageView as a subview. Then you can create as much spacing around the image as you want.
class CustomView: UIView {
lazy var imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
init(frame: CGRect, corners: CACornerMask, systemName: String) {
super.init(frame: frame)
self.setUpViews()
// ...
}
func setUpViews() {
addSubview(imageView)
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5),
imageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5),
imageView.topAnchor.constraint(equalTo: topAnchor, constant: 5),
imageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5),
])
}
func createImage(systemName: String) {
let image = UIImage(systemName: systemName)!
self.imageView.image = image
}
}

swipe between UIView with segemented control programmatically

hi fellow developer I want to swipe between uiview with scrollView while changing the the index and small indicator move left and right. but is not responding while I try to swipe. this is my picture and my setup to scroll between uiview. can you tell me where I am missing?
let segmentedControl = UISegmentedControl()
let containerView = UIView()
let indicatorView = UIView()
let leftView = UIView()
let rightView = UIView()
let scrollView = UIScrollView()
var slides = [UIView]()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
slides.append(leftView)
slides.append(rightView)
segmentedControl.insertSegment(withTitle: "Harian", at: 0, animated: true)
segmentedControl.insertSegment(withTitle: "Mata Pelajaran", at: 1, animated: true)
segmentedControl.selectedSegmentIndex = 0
segmentedControl.addTarget(self, action: #selector(handleTap), for: .valueChanged)
setupSlideScrollView()
}
func setupSlideScrollView() {
view.addSubview(scrollView)
scrollView.isPagingEnabled = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
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])
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x / view.frame.width)
segmentedControl.selectedSegmentIndex = Int(pageIndex)
}
#objc func handleTap(_ sender: UISegmentedControl) {
indicatorView.frame.origin.x = (segmentedControl.frame.width / CGFloat(segmentedControl.numberOfSegments)) * CGFloat(segmentedControl.selectedSegmentIndex)
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
self.indicatorView.transform = CGAffineTransform(scaleX: 0.7, y: 1)
}) { (finish) in
UIView.animate(withDuration: 0.4, animations: {
self.indicatorView.transform = CGAffineTransform.identity
})
}
}

ScrollView doesn't scroll swift

I have one method that shows a photo and zooms in it if the user wants. I used a scroll view so that the user could scroll if he zooms in a photo. My zoom works but it zoom into the top left corner and my scroll view doesn't scroll anywhere else even though it shows vertical and horizontal bars responsible for scroll moving.
class ImageViewController: UIViewController, UIScrollViewDelegate {
var myscrollView: UIScrollView!
var imgView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
myscrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
self.view.addSubview(myscrollView)
myscrollView.delegate = self
// myscrollView.scrollRectToVisible(CGRect(x: 100, y: 100, width: 1, height: 1), animated: true)
// myscrollView.setContentOffset(CGPoint(x: 0, y: view.frame.size.height), animated: true)
// myscrollView.contentSize = CGSize(width: view.frame.width, height: view.frame.height)
print("\(myscrollView.frame.height) \(myscrollView.frame.width)")
//myscrollView.contentSize = CGSize(width: 1000, height: 2000)
myscrollView.isScrollEnabled = true
myscrollView.minimumZoomScale = 1.0
myscrollView.maximumZoomScale = 10.0//maximum zoom scale you want
myscrollView.zoomScale = 1.0
myscrollView.alwaysBounceVertical = false
myscrollView.alwaysBounceHorizontal = false
myscrollView.showsVerticalScrollIndicator = true
myscrollView.flashScrollIndicators()
// scrollView.isPagingEnabled = false
// self.scrollView.contentSize = self.view.frame.size * 2//or what ever size you want to set
// myscrollView.contentOffset = CGPointMake((myscrollView.contentSize.width-myscrollView.frame.size.width)*.5f, .0f);//scroll to center
// myscrollView.contentOffset = CGPoint((myscrollView.contentSize.width-myscrollView.frame.size.width)*.5f, .0f)
myscrollView.addSubview(imgView)
//self.view.addSubview(imgView)
imgView!.layer.cornerRadius = 11.0
imgView.bounds = myscrollView.bounds
//imgView!.clipsToBounds = false
imgView.isUserInteractionEnabled = true
imgView.heightAnchor.constraint(equalToConstant: 50).isActive = true
// imgView.widthAnchor.constraint(equalToConstant: 50).isActive = true
imgView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 64).isActive = true
imgView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true
imgView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
// imgView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
imgView.translatesAutoresizingMaskIntoConstraints = false
myscrollView.contentSize = CGSize(width: imgView.frame.width , height: 2000)
// myscrollView.contentSize = imgView.bounds.size
// Do any additional setup after loading the view.
}
// func scrollViewDidZoom(_ scrollView: UIScrollView) {
// var offsetY: CGFloat!
// offsetY = 0;
// if (scrollView.zoomScale > 1){
// offsetY = CGFloat(scrollView.contentOffset.y);
// }
// //CGPoint(x: scrollView.contentOffset.x, y: offsetY)
// scrollView.setContentOffset(CGPoint(x:
// scrollView.contentOffset.x, y: offsetY), animated: true)
//
// // [aScrollView setContentOffset:
// CGPointMake(aScrollView.contentOffset.x, offsetY)];
//
// }
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imgView
}
// override func viewDidLayoutSubviews() {
// myscrollView.delegate = self
// myscrollView.contentSize =
//CGSize(width:self.view.frame.size.width, height: 1000)
// }
// func viewForZooming(in scrollView: UIScrollView) -> UIView? {
// return imgView
// }
}
I tried to set contentSize manually to some big or small values and well as using frame.width and height but none of them seem to be working. Can someone please help what is my problem? Thanks in advance :)
Just try these lines of code and check, What are you missing-
let screenSize: CGRect = UIScreen.main.bounds
let scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.contentSize = CGSize(width: screenSize.width, height: 1300)
scrollView.frame = CGRect(x: 0, y: 70, width: screenSize.width, height: screenSize.height-70)
scrollView.backgroundColor = UIColor.clear
view.addSubview(scrollView)
}
Try implementing scrollViewDidZoom method as follows;
func scrollViewDidZoom(_ scrollView: UIScrollView) {
let offsetX = max((scrollView.bounds.size.width-scrollView.contentSize.width) * 0.5, 0.0)
let offsetY = max((scrollView.bounds.size.height-scrollView.contentSize.height) * 0.5, 0.0)
self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0, 0)
}