Image view moves to aside when scrolling vertically in scroll view - ios

I've got a view controller that has a UIView (featuredStoryView) and inside that view, I've added a scroll view (scrollView) and inside the scroll view, I've added an ImageView (bookCover), one on top of the other. Here is the code for all of those elements:
func setupFeaturedStoryView() {
featuredStoryView.backgroundColor = .white
featuredStoryView.layer.cornerRadius = 12
// Let the user tap on the featured story image view
featuredStoryView.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(featuredStoryViewTapped))
featuredStoryView.addGestureRecognizer(tapGesture)
view.addSubview(featuredStoryView)
addFeaturedStoryViewConstraints()
}
func setupScrollView() {
scrollView.backgroundColor = UIColor(red: 10/255, green: 10/255, blue: 20/255, alpha: 0.5)
scrollView.layer.cornerRadius = 15
featuredStoryView.addSubview(scrollView)
// Add the constraints to the scroll view
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.topAnchor.constraint(equalTo: featuredStoryView.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: featuredStoryView.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: featuredStoryView.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: featuredStoryView.bottomAnchor).isActive = true
}
func setupBookCover() {
bookCover.backgroundColor = .yellow
bookCover.layer.cornerRadius = 15
bookCover.contentMode = .scaleAspectFill
scrollView.addSubview(bookCover)
addBookCoverConstraints()
}
// Add the constraints to the featured story view
func addFeaturedStoryViewConstraints() {
featuredStoryView.translatesAutoresizingMaskIntoConstraints = false
featuredStoryView.topAnchor.constraint(equalTo: view.centerYAnchor, constant: -130).isActive = true
featuredStoryView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100).isActive = true
featuredStoryView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -100).isActive = true
featuredStoryView.bottomAnchor.constraint(equalTo: view.centerYAnchor, constant: 130).isActive = true
}
// Add constraints to the book cover
func addBookCoverConstraints() {
bookCover.translatesAutoresizingMaskIntoConstraints = false
bookCover.topAnchor.constraint(equalTo: featuredStoryView.topAnchor).isActive = true
bookCover.leadingAnchor.constraint(equalTo: featuredStoryView.leadingAnchor).isActive = true
bookCover.trailingAnchor.constraint(equalTo: featuredStoryView.trailingAnchor).isActive = true
bookCover.bottomAnchor.constraint(equalTo: featuredStoryView.bottomAnchor).isActive = true
}
When you tap on the featuredStoryView, the following code snippet runs:
#objc func featuredStoryViewTapped() {
scrollView.contentSize.height = 1500
let animator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 0.8) {
self.featuredStoryView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
self.scrollView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
self.view.layoutIfNeeded()
self.bookCover.frame = CGRect(x: self.featuredStoryView.frame.midX - (bookCoverWidth / 2), y: 90, width: bookCoverWidth, height: bookCoverHeight)
self.view.layoutIfNeeded()
}
animator.startAnimation()
}
Now take a look at this Gif:
The problem here is, when I scroll, the yellow imageView(bookCover) moves to the top left corner, why does this happen? It should just go up with the scroll view. Is there any way to fix this?

You can not set frame directly like self.featuredStoryView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height) and contentSize like scrollView.contentSize.height = 1500 in auto layout. So try to set constraints as per your requirement.

Related

Cant get dynamic scroll view that sizes to its content working working

I'm having an issue getting my scroll view to to dynamically size and scroll to fit all of my content. I'm doing it all programmatically as I find it the easiest to use when dealing with auto layout. Anyways every solution I've come across online doesn't seem to work and the closest I got was when I tried this. contentView.heightAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.heightAnchor).isActive = true Setting the height anchor of the context view to the frame layout guide of the scroll view allowed me to scroll but it still didn't reach the bottom. I feel like I must be overlooking something but I can't figure it out for the life of me. Here is my code below.Thanks Also sorry about how it's displayed below I can't get it to all fit in the box.
Edit: Prior to posting this I was experimenting with embedding my content view in a stack view which is why it's there but I have tried it without the stack and it still didn't work.
Edit 2: Found a solution and fixed how the code was displayed.
import UIKit
import Charts
class ViewController: UIViewController, UIScrollViewDelegate, ChartViewDelegate {
let scrollView = UIScrollView()
let contentStackView = UIStackView()
let contentView = UIView()
var dealStack, customScoreStack, basicInfoStack: UIStackView!
var thisListing = Listing()
var priceDollarSign, dealIndicatorArrow, vehicleImage, customScoreLogo: UIImageView!
var vehiclePrice, dealType, dealValueLbl, milesLbl, customScoreValue, customScoreTitle, basicCarInfo, distanceAndDealer, graphTitle, specTitle, insuranceCalcTitle, commentsTitle, similarListingsTitle: UILabel!
var graphSwitchingSegment, yearSwitchingSegment1, yearSwitchingSegment2: UISegmentedControl!
var carPrice = "", dealValue = "1", miles = "", year = "2021", make = "GMC", model = "YUKON", trim = "DENALI", distance = "20", dealerName = "Dealer"
var graphState = true
var graphData1, graphData2: String!
lazy var graph1: LineChartView = {
let chartView = LineChartView()
return chartView
}()
lazy var graph2: LineChartView = {
let chartView = LineChartView()
return chartView
}()
var months: [String]!
var yValues: [ChartDataEntry]!
override func viewDidLoad() {
super.viewDidLoad()
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
yValues = [
ChartDataEntry(x: 1, y: 20.0),
ChartDataEntry(x: 2, y: 4.0),
ChartDataEntry(x: 3, y: 6.0),
ChartDataEntry(x: 4, y: 3.0),
ChartDataEntry(x: 5, y: 12.0),
ChartDataEntry(x: 6, y: 16.0),
ChartDataEntry(x: 7, y: 4.0),
ChartDataEntry(x: 8, y: 18.0),
ChartDataEntry(x: 9, y: 2.0),
ChartDataEntry(x: 10, y: 4.0),
ChartDataEntry(x: 11, y: 5.0),
ChartDataEntry(x: 12, y: 4.0)
]
self.scrollView.backgroundColor = .lightGray
self.view.addSubview(scrollView)
self.view.sendSubviewToBack(scrollView)
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.isScrollEnabled = true
self.scrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
self.scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.scrollView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor).isActive = true
self.scrollView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor).isActive = true
self.scrollView.addSubview(contentStackView)
self.contentStackView.translatesAutoresizingMaskIntoConstraints = false
self.contentStackView.axis = .vertical
self.contentStackView.topAnchor.constraint(equalTo: self.scrollView.topAnchor).isActive = true
self.contentStackView.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor).isActive = true
self.contentStackView.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor).isActive = true
self.contentStackView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor).isActive = true
self.contentStackView.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
contentStackView.addArrangedSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.topAnchor.constraint(equalTo: self.contentStackView.topAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: self.contentStackView.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: self.contentStackView.trailingAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: self.contentStackView.bottomAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: self.contentStackView.widthAnchor).isActive = true
//contentView.heightAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.heightAnchor).isActive = true
contentView.sizeToFit()
setUpView()
graph1Setup()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.delegate = self
print("View Size: \(self.view.frame.debugDescription) \nScroll View Size: \(self.scrollView.frame.debugDescription) \nScroll Content View Size: \(self.contentView.frame.debugDescription)")
}
override func viewDidAppear(_ animated: Bool) {
// Test subview locations here: print(_SUBVIEW.frame.debugDescription)
}
#objc func graphSwitcher(_ segmentedControl: UISegmentedControl) {
switch graphSwitchingSegment.selectedSegmentIndex {
case 0:
graphState = true
print("VALUE CHANGED")
print(self.contentView.frame.debugDescription)
case 1:
graphState = false
print("VALUE CHANGED")
default:
graphState = true
break
}
}
func setUpView() {
//Object Instantiation
dealStack = UIStackView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
dealStack.translatesAutoresizingMaskIntoConstraints = false
dealStack.axis = .vertical
dealStack.spacing = 1
dealStack.distribution = .fillProportionally
customScoreStack = UIStackView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
customScoreStack.translatesAutoresizingMaskIntoConstraints = false
customScoreStack.axis = .vertical
customScoreStack.spacing = 1
customScoreStack.distribution = .fillProportionally
basicInfoStack = UIStackView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
basicInfoStack.translatesAutoresizingMaskIntoConstraints = false
basicInfoStack.axis = .vertical
basicInfoStack.spacing = 2
basicInfoStack.distribution = .fillProportionally
priceDollarSign = UIImageView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
priceDollarSign.image = UIImage(named: "greenDollarsign")
priceDollarSign.translatesAutoresizingMaskIntoConstraints = false
dealIndicatorArrow = UIImageView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
dealIndicatorArrow.image = UIImage(named: "greenUpArrow")
dealIndicatorArrow.translatesAutoresizingMaskIntoConstraints = false
vehicleImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
vehicleImage.image = UIImage(named: "audiA7")
vehicleImage.translatesAutoresizingMaskIntoConstraints = false
customScoreLogo = UIImageView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
customScoreLogo.image = UIImage(named: "aladdinGenie")
customScoreLogo.translatesAutoresizingMaskIntoConstraints = false
customScoreTitle = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
customScoreTitle.text = "Genie Score:"
customScoreTitle.textAlignment = .right
customScoreTitle.translatesAutoresizingMaskIntoConstraints = false
customScoreValue = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
customScoreValue.text = "VALUE_PLACE_HOLDER"
customScoreValue.translatesAutoresizingMaskIntoConstraints = false
vehiclePrice = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
vehiclePrice.text = numberFormatter.string(from: NSNumber(value: thisListing.getDetails().returnCarPrice()))
vehiclePrice.font = UIFont.systemFont(ofSize: 30, weight: .semibold)
vehiclePrice.textAlignment = .left
vehiclePrice.translatesAutoresizingMaskIntoConstraints = false
dealType = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
dealType.text = thisListing.getDetails().returnDealRatingType().0
dealType.font = UIFont(name: dealType.font.fontName, size: 28)
dealType.textColor = self.thisListing.getDetails().returnDealRatingType().1
dealType.adjustsFontSizeToFitWidth = true
dealType.minimumScaleFactor = 0.8
dealType.numberOfLines = 0
dealType.textAlignment = .right
dealType.translatesAutoresizingMaskIntoConstraints = false
dealValueLbl = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
dealValueLbl.text = "$" + (numberFormatter.string(from: NSNumber(value: self.thisListing.getDetails().returnDealDiff()))!) + (self.thisListing.getDetails().returnDealDiff() > 0 ? " ABOVE":" BELOW")
dealValueLbl.font = UIFont(name: dealValueLbl.font.fontName, size: 19)
dealValueLbl.adjustsFontSizeToFitWidth = true
dealValueLbl.minimumScaleFactor = 0.8
dealValueLbl.numberOfLines = 0
dealValueLbl.textAlignment = .right
dealValueLbl.translatesAutoresizingMaskIntoConstraints = false
milesLbl = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
milesLbl.font = UIFont.systemFont(ofSize: 34, weight: .regular)
milesLbl.textAlignment = .left
milesLbl.adjustsFontSizeToFitWidth = true
milesLbl.minimumScaleFactor = 0.8
milesLbl.text = "\(numberFormatter.string(from: NSNumber(value: (self.miles as NSString).integerValue)) ?? "") Miles"
milesLbl.translatesAutoresizingMaskIntoConstraints = false
basicCarInfo = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
basicCarInfo.text = "\(year) \(make) \(model) \(trim)"
basicCarInfo.font = UIFont.systemFont(ofSize: 20, weight: .semibold)
basicCarInfo.adjustsFontSizeToFitWidth = true
basicCarInfo.numberOfLines = 0
basicCarInfo.minimumScaleFactor = 0.8
basicCarInfo.textAlignment = .center
basicCarInfo.translatesAutoresizingMaskIntoConstraints = false
distanceAndDealer = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
distanceAndDealer.text = "\(distance) miles away from you at \(dealerName)"
distanceAndDealer.font = UIFont.systemFont(ofSize: 20, weight: .regular)
distanceAndDealer.adjustsFontSizeToFitWidth = true
distanceAndDealer.numberOfLines = 0
distanceAndDealer.minimumScaleFactor = 0.8
distanceAndDealer.textAlignment = .center
distanceAndDealer.translatesAutoresizingMaskIntoConstraints = false
graphTitle = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
graphTitle.text = "Graphs"
graphTitle.font = UIFont.systemFont(ofSize: 22, weight: .semibold)
graphTitle.numberOfLines = 0
graphTitle.adjustsFontSizeToFitWidth = true
graphTitle.minimumScaleFactor = 0.8
graphTitle.textAlignment = .center
graphTitle.translatesAutoresizingMaskIntoConstraints = false
graphSwitchingSegment = UISegmentedControl(items: ["Graph 1","Graph 2"])
graphSwitchingSegment.addTarget(self, action: #selector(graphSwitcher(_:)), for: .valueChanged)
graphSwitchingSegment.selectedSegmentIndex = 0
graphSwitchingSegment.translatesAutoresizingMaskIntoConstraints = false
graph1.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
graph1.noDataText = "OOPS NO DATA HERE!"
graph1.noDataTextColor = .green
graph1.backgroundColor = .white
graph1.translatesAutoresizingMaskIntoConstraints = false
yearSwitchingSegment1 = UISegmentedControl(items: ["Year 1","Year 2"])
yearSwitchingSegment1.addTarget(self, action: #selector(graphSwitcher(_:)), for: .valueChanged)
yearSwitchingSegment1.selectedSegmentIndex = 0
yearSwitchingSegment1.translatesAutoresizingMaskIntoConstraints = false
//Adds objects to the view
self.contentView.addSubview(priceDollarSign)
self.contentView.addSubview(dealStack)
self.contentView.addSubview(customScoreStack)
self.contentView.addSubview(basicInfoStack)
self.contentView.addSubview(dealIndicatorArrow)
self.contentView.addSubview(vehicleImage)
self.contentView.addSubview(vehiclePrice)
self.contentView.addSubview(milesLbl)
self.contentView.addSubview(customScoreLogo)
self.contentView.addSubview(graphTitle)
self.contentView.addSubview(graphSwitchingSegment)
self.contentView.addSubview(graph1)
self.contentView.addSubview(yearSwitchingSegment1)
dealStack.addArrangedSubview(dealType)
dealStack.addArrangedSubview(dealValueLbl)
customScoreStack.addArrangedSubview(customScoreTitle)
customScoreStack.addArrangedSubview(customScoreValue)
basicInfoStack.addArrangedSubview(basicCarInfo)
basicInfoStack.addArrangedSubview(distanceAndDealer)
//Object Constraints
priceDollarSign.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 7).isActive = true
priceDollarSign.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: 7).isActive = true
priceDollarSign.widthAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.widthAnchor, multiplier: 0.1).isActive = true
priceDollarSign.widthAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = true
priceDollarSign.widthAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true
priceDollarSign.heightAnchor.constraint(equalTo: priceDollarSign.widthAnchor).isActive = true
priceDollarSign.heightAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = true
priceDollarSign.heightAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true
vehiclePrice.topAnchor.constraint(equalTo: priceDollarSign.topAnchor).isActive = true
vehiclePrice.bottomAnchor.constraint(equalTo: priceDollarSign.bottomAnchor).isActive = true
vehiclePrice.leadingAnchor.constraint(equalTo: priceDollarSign.trailingAnchor, constant: 8).isActive = true
vehiclePrice.sizeToFit()
vehiclePrice.layoutIfNeeded()
dealStack.topAnchor.constraint(equalTo: priceDollarSign.topAnchor).isActive = true
dealStack.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, constant: -7).isActive = true
dealStack.heightAnchor.constraint(equalTo: priceDollarSign.heightAnchor).isActive = true
dealType.heightAnchor.constraint(equalTo: dealValueLbl.heightAnchor).isActive = true
dealType.widthAnchor.constraint(equalTo: dealValueLbl.widthAnchor).isActive = true
dealType.sizeToFit()
dealType.layoutIfNeeded()
dealValueLbl.sizeToFit()
dealValueLbl.layoutIfNeeded()
dealIndicatorArrow.topAnchor.constraint(equalTo: priceDollarSign.topAnchor).isActive = true
dealIndicatorArrow.bottomAnchor.constraint(equalTo: priceDollarSign.bottomAnchor).isActive = true
dealIndicatorArrow.trailingAnchor.constraint(equalTo: dealType.leadingAnchor, constant: -1).isActive = true
dealIndicatorArrow.widthAnchor.constraint(equalTo: priceDollarSign.widthAnchor).isActive = true
vehicleImage.topAnchor.constraint(equalTo: priceDollarSign.bottomAnchor, constant: 8).isActive = true
vehicleImage.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor).isActive = true
vehicleImage.widthAnchor.constraint(equalTo: self.contentView.widthAnchor).isActive = true
vehicleImage.heightAnchor.constraint(equalTo: self.vehicleImage.widthAnchor, multiplier: 9/16).isActive = true
vehicleImage.heightAnchor.constraint(lessThanOrEqualToConstant: self.view.frame.height * 0.3).isActive = true
vehicleImage.contentMode = .scaleAspectFit
milesLbl.topAnchor.constraint(equalTo: vehicleImage.bottomAnchor, constant: 8).isActive = true
milesLbl.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 8).isActive = true
milesLbl.sizeToFit()
milesLbl.layoutIfNeeded()
customScoreStack.topAnchor.constraint(equalTo: milesLbl.topAnchor).isActive = true
customScoreStack.heightAnchor.constraint(equalTo: milesLbl.heightAnchor).isActive = true
customScoreStack.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, constant: -8).isActive = true
customScoreLogo.topAnchor.constraint(equalTo: milesLbl.topAnchor).isActive = true
customScoreLogo.trailingAnchor.constraint(equalTo: customScoreStack.leadingAnchor, constant: 1).isActive = true
customScoreLogo.widthAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.widthAnchor, multiplier: 0.1).isActive = true
customScoreLogo.widthAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = true
customScoreLogo.widthAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true
customScoreLogo.heightAnchor.constraint(equalTo: customScoreLogo.widthAnchor).isActive = true
customScoreLogo.heightAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = true
customScoreLogo.heightAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true
basicInfoStack.topAnchor.constraint(equalTo: milesLbl.bottomAnchor, constant: 32).isActive = true
basicInfoStack.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
basicInfoStack.widthAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.widthAnchor, constant: -10).isActive = true
basicCarInfo.heightAnchor.constraint(equalTo: distanceAndDealer.heightAnchor).isActive = true
graphTitle.topAnchor.constraint(equalTo: basicInfoStack.bottomAnchor, constant: 24).isActive = true
graphTitle.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
graphTitle.widthAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.widthAnchor, constant: -16).isActive = true
graphSwitchingSegment.topAnchor.constraint(equalTo: graphTitle.bottomAnchor, constant: 20).isActive = true
graphSwitchingSegment.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
graphSwitchingSegment.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
graph1.topAnchor.constraint(equalTo: graphSwitchingSegment.bottomAnchor, constant: 2).isActive = true
graph1.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
graph1.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
graph1.heightAnchor.constraint(equalTo: self.graph1.widthAnchor, multiplier: 12/16).isActive = true
yearSwitchingSegment1.topAnchor.constraint(equalTo: graph1.bottomAnchor, constant: 100).isActive = true
yearSwitchingSegment1.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
yearSwitchingSegment1.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
}
func graph1Setup() {
let dataSet = LineChartDataSet(entries: yValues, label: "Months")
let data = LineChartData(dataSet: dataSet)
graph1.data = data
}
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
print(entry)
}
}
I found a solution that worked and turns out it was something small that I missed which blocked me for hours. I created a UIScrollView and UIView extension with a function that sorts the subviews by maxY of each and resizes the content view and height to be equal to that. The reason it wasn't working earlier is because the scroll view only had one subview which was a container view that housed all of the UI components. I'm not sure exactly why the container view still wasn't growing with it's subviews because whenever I would get rid of any height constraints it would end up being zero and the sub views would just be out of the bounds but with this fix it seems to work fine now. Heres what I did and the link to where I found help.
Extensions
extension UIScrollView {
func updateContentView() {
contentSize.height = subviews.sorted(by: { $0.frame.maxY < $1.frame.maxY }).last?.frame.maxY ?? contentSize.height
}
}
extension UIView {
func updateHeightToFitContent() {
frame.size.height = subviews.sorted(by: { $0.frame.maxY < $1.frame.maxY }).last?.frame.maxY ?? frame.size.height
}
}
viewDidLayoutSubviews()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.delegate = self
self.view.layoutIfNeeded() //MAKE SURE YOU ADD THIS IT'S NEEDED
contentView.updateHeightToFitContent()
scrollView.updateContentView()
}
You don't need to do any height calculating... nor do you need to be setting contentSize.height.
Give the bottom element a bottom constraint.
In the code you've shown, add this line at the end of your constraints setup:
yearSwitchingSegment1.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -20).isActive = true
After adding that line, get rid of both of those extensions, and get rid of viewDidLayoutSubviews() - you can put scrollView.delegate = self in viewDidLoad().
I used some constant strings since I don't have your
`var thisListing = Listing()`
code, but this is what I get WITHOUT that constraint:
and cannot scroll.
This is what I get WITH that constraint, and as you can see, I can now scroll to the bottom of your content:

Calling 'scrollView.zoom' Does Not Zoom

For some reason scrollView.zoom does not zoom in on an imageView.
I have an imageView inside a scrollView.
in ViewDidLoad:
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 2.0
scrollView.delegate = self
viewForZooming:
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
Now, I'm calling below after both scrollView and imageView are initialized.
var scale: CGFloat = 2
let location = CGPoint(x: imageView.frame.width/2, y: imageView.frame.height/2)
scrollView.zoom(to: zoomRectForScale(scale, center: location), animated: true)
Nothing is happening after scrollView.zoom is called. I tried doing
view.setNeedsDisplay()
view.layoutIfNeeded()
Still nothing happens, imageView is not zooming in.
UPDATE:
As requested, here is the code for scrollView and imageView initialization:
func createscrollView(image: UIImage?){
if image == nil{
let img = UIImage(named: "demo image.jpg")
let imgCorrected = scaleAndOrient(image: img!)
//created user selecged images
userSelectedImage_UI = img
userSelectedImage_UI_Corrected = imgCorrected
}
// create image scroll view
scrollView = UIScrollView(frame: CGRect(x: 0, y: 0,width: 100, height: 100))
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
scrollView.bouncesZoom = false
scrollView.bounces = false
scrollView.contentMode = .scaleAspectFill
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0),
scrollView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0),
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -150),
scrollView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0)
])
// add image view to scrollview
imageView = UIImageView(frame: CGRect(x: 0, y: 0,width: 100, height: 100))
scrollView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 0),
imageView.leftAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leftAnchor, constant: 0),
imageView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: 0),
imageView.rightAnchor.constraint(equalTo: scrollView.contentLayoutGuide.rightAnchor, constant: 0),
imageView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1),
imageView.heightAnchor.constraint(equalTo: scrollView.heightAnchor, multiplier: 1)
])
imageView.contentMode = .scaleAspectFit
if image == nil{
imageView.image = userSelectedImage_UI_Corrected
} else {
imageView.image = scaleAndOrient(image: image!)
}
}
Based on comments...
It sounds like you are creating / setting up your scrollView and its content imageView from viewDidLoad() and then immediately trying to "zoom" it.
If so, that will be problematic, as auto-layout hasn't finished laying out the UI elements.
You can call it from:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
var scale: CGFloat = 2
let location = CGPoint(x: imageView.frame.width/2, y: imageView.frame.height/2)
scrollView.zoom(to: zoomRectForScale(scale, center: location), animated: true)
}
I have a feeling the problem is in your zoomRectForScale function, though it isn't posted. It should be something like this:
func zoomRectForScale(_ scale: CGFloat, center: CGPoint) -> CGRect {
var zoomRect = CGRect.zero
zoomRect.size.height = imageView.frame.size.height / scale
zoomRect.size.width = imageView.frame.size.width / scale
let newCenter = scrollView.convert(center, from: imageView)
zoomRect.origin.x = newCenter.x - (zoomRect.size.width / 2.0)
zoomRect.origin.y = newCenter.y - (zoomRect.size.height / 2.0)
return zoomRect
}
Additionally, I've verified that double-tapping to zoom works to the correct location using:
var gestureRecognizer: UITapGestureRecognizer!
// Sets up the gesture recognizer that receives double taps to auto-zoom
func setupGestureRecognizer() {
gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
gestureRecognizer.numberOfTapsRequired = 2
scrollView.addGestureRecognizer(gestureRecognizer)
}
// Handles a double tap by either resetting the zoom or zooming to where was tapped
#IBAction func handleDoubleTap() {
if scrollView.zoomScale == 1 {
scrollView.zoom(to: zoomRectForScale(scrollView.maximumZoomScale, center: gestureRecognizer.location(in: gestureRecognizer.view)), animated: true)
} else {
scrollView.setZoomScale(1, animated: true)
}
}
Don't forget to call this in viewDidLoad:
setupGestureRecognizer()
If this doesn't solve your issue, please adjust your question with missing code functions and variable declarations.

how to set size of a scrollview to same size of a view

I have a view and inside the view there is a scrollview. I setup the scrollview programmatically. But for some reason the scrollview fits not perfectly in the view. The scrollview has the same frame as the view. But for some reason it is not working.
The white view is the view where the scrollview is in it.
The scrollview is the green view. I set the background color to green.
In the scrollview there is an image view.
My Code:
var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.alwaysBounceVertical = false
scrollView.alwaysBounceHorizontal = false
scrollView.isPagingEnabled = true
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.backgroundColor = .green
scrollView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: contentView.frame.height) contentView.addSubview(scrollView)
scrollView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 0).isActive = true
scrollView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0).isActive = true
scrollView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: 0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0).isActive = true
}
var frame = CGRect.zero
func viewTutorial() {
for i in 0..<arrayOfTutorilImages.count {
frame.origin.x = scrollView.frame.size.width * CGFloat((i))
frame.size = scrollView.frame.size
let imageView = UIImageView(frame: frame)
imageView.image = UIImage(named: arrayOfTutorilImages[i])
imageView.contentMode = .scaleAspectFit
self.scrollView.addSubview(imageView)
}
scrollView.contentSize = CGSize(width: (scrollView.frame.size.width * CGFloat(arrayOfTutorilImages.count)), height: scrollView.frame.size.height)
scrollView.delegate = self
}
extension TutorialViewController: UIScrollViewDelegate {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
pageControl.currentPage = Int(pageNumber)
}
}
UPDATE:
I changed the frame and added constraints to the scrollview. Now it look like this. The images is not resizing (this image is the blue drawing)
I would recommend you to use constrains:
self.scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
self.scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor),
self.scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
You can create a helper method for this if you have like a UIView+Helpers, and use just one line.
extension UIView {
public func pinToEdges(of view: UIView,
topConstant: CGFloat = 0,
leadingConstant: CGFloat = 0,
bottomConstant: CGFloat = 0,
trailingConstant: CGFloat = 0) {
NSLayoutConstraint.activate([
self.topAnchor.constraint(equalTo: view.topAnchor, constant: topConstant),
self.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: leadingConstant),
self.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: bottomConstant),
self.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: trailingConstant),
])
}
}
And then use:
self.scrollView.pinToBounds(self.view)
And remember of course to set the translatesAutoresizingMaskIntoConstraints to false

My textView bottomAnchor does not seem to work?

I have a textView and I have a line, I set the line's frame without contraints and set textView frame with constraints. Simply what I want is the textView to follow the line, so I put a bottomAnchor to textView equal to the topAnchor of the line. Yet when I animate the line the textView does not follow? What am I doing wrong?
var button = UIButton()
var testLine = UIView()
let textView = UITextView()
var textViewBottomAnchorConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
testLine.backgroundColor = .black
testLine.frame = CGRect(x: 0, y: 335, width: UIScreen.main.bounds.width, height: 10)
view.addSubview(testLine)
view.addSubview(textView)
textView.frame = .zero//CGRect(x: CGFloat(integerLiteral: 16), y: CGFloat(integerLiteral: 300), width: CGFloat(integerLiteral: 282), height: CGFloat(integerLiteral: 35))
textView.backgroundColor = UIColor.yellow
textView.text = ""
textView.font = UIFont(name: "Arial Rounded MT Bold", size: 15)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isHidden = false
textView.translatesAutoresizingMaskIntoConstraints = false
// textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0).isActive = true
textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor, constant: 20).isActive = true
textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: -20).isActive = true
textView.heightAnchor.constraint(equalToConstant: 40).isActive = true
textViewBottomAnchorConstraint = textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0)
textViewBottomAnchorConstraint?.isActive = true
UIView.animate(withDuration: 2, delay: 2, options: .curveEaseIn, animations: {
self.testLine.transform = CGAffineTransform.identity.translatedBy(x: 0, y: 30)
}) { (true) in
self.view.layoutIfNeeded()
}
}
As #Vollan correctly said animating transform property is not the best option. Here is quote from Apple documentation: "In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame." Therefore animation of transform property doesn't change layout of textView. I recommend you to animate frame property instead of transform.
However, if you switch to frame animation it doesn't fix all your problems. If you keep your animation inside viewDidLoad method you may encounter very strange behavior. The reason is that in viewDidLoad the view itself is not yet laid out properly. Starting animation inside viewDidLoad may lead to unpredicted results.
At last you need adjust your animation block. Apple recommends to apply layoutIfNeeded inside the animation block. Or at least they used to recommend it then autolayout was introduced - watch this WWDC video (starting from 30th minute) for further details.
If you apply all recommendations above your code should look like this:
var button = UIButton()
var testLine = UIView()
let textView = UITextView()
var textViewBottomAnchorConstraint: NSLayoutConstraint?
var triggeredAnimation = false
override func viewDidLoad() {
super.viewDidLoad()
testLine.backgroundColor = .black
testLine.frame = CGRect(x: 0, y: 335, width: UIScreen.main.bounds.width, height: 10)
view.addSubview(testLine)
view.addSubview(textView)
textView.frame = .zero//CGRect(x: CGFloat(integerLiteral: 16), y: CGFloat(integerLiteral: 300), width: CGFloat(integerLiteral: 282), height: CGFloat(integerLiteral: 35))
textView.backgroundColor = UIColor.yellow
textView.text = ""
textView.font = UIFont(name: "Arial Rounded MT Bold", size: 15)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isHidden = false
textView.translatesAutoresizingMaskIntoConstraints = false
// textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0).isActive = true
textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor, constant: 20).isActive = true
textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: -20).isActive = true
textView.heightAnchor.constraint(equalToConstant: 40).isActive = true
textViewBottomAnchorConstraint = textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0)
textViewBottomAnchorConstraint?.isActive = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// viewDidAppear may be called several times during view controller lifecycle
// triggeredAnimation ensures that animation will be called just once
if self.triggeredAnimation {
return
}
self.triggeredAnimation = true
let oldFrame = self.testLine.frame
UIView.animate(withDuration: 2, delay: 2, options: .curveEaseIn, animations: {
self.testLine.frame = CGRect(x: oldFrame.minX, y: oldFrame.minY + 30, width: oldFrame.width,
height: oldFrame.height)
self.view.layoutIfNeeded()
})
}
Anchor points make references to others positions, meaning. It is still referensed to y = 355 as you transform it and not actually "move" it.
What i recommend is that you don't mix using frame-based layout and anchorpoints / layout constraints.

How can I create reusable view controllers inside of UIScrollView?

I am trying to create a full page sized horizontally scrolling UIScrollView. On each page I am adding instances of the same UIViewController class. I would like to create some kind of reusable functionality to conserve both memory and processor use needed. Below is a basic implementation I have created with some toying around with how reusability might work although Im not quite sure. Thank you for any help you can offer.
Current UIScroll ViewController Model
let scrollView:UIScrollView = {
let scrollView = UIScrollView(frame: CGRect.zero)
scrollView.isPagingEnabled = true
scrollView.backgroundColor = UIColor.gray
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = true
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
scrollView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: self.view.bounds.size)
self.view.addSubview(scrollView)
scrollView.delegate = self
scrollView.contentSize = CGSize(width: 3 * self.view.frame.width, height: self.view.frame.height)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0),
scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0),
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0),
scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
])
let viewController1 = UIViewController()
viewController1.view.backgroundColor = UIColor.red
let viewController2 = UIViewController()
viewController2.view.backgroundColor = UIColor.blue
let viewController3 = UIViewController()
viewController3.view.backgroundColor = UIColor.green
self.addChild(viewController1)
viewController1.view.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: self.view.bounds.size)
scrollView.addSubview(viewController1.view)
addViewControllerContraints(viewController: viewController1, index: 0)
viewController1.didMove(toParent: self)
self.addChild(viewController2)
viewController2.view.frame = CGRect(origin: CGPoint(x: self.view.bounds.width, y: 0), size: self.view.bounds.size)
scrollView.addSubview(viewController2.view)
addViewControllerContraints(viewController: viewController2, index: 1)
viewController2.didMove(toParent: self)
self.addChild(viewController3)
viewController3.view.frame = CGRect(origin: CGPoint(x: 2 * self.view.bounds.width, y: 0), size: self.view.bounds.size)
scrollView.addSubview(viewController3.view)
addViewControllerContraints(viewController: viewController3, index: 2)
viewController3.didMove(toParent: self)
}
func addViewControllerContraints( viewController: UIViewController, index:Int){
guard let view = viewController.view else{
print("View found nil")
return
}
view.translatesAutoresizingMaskIntoConstraints = false
let offset:CGFloat = UIScreen.main.bounds.width * CGFloat(index)
print("Offset: \(offset)")
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0),
view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: offset),
view.heightAnchor.constraint(equalToConstant: self.view.bounds.height),
view.widthAnchor.constraint(equalToConstant: self.view.bounds.width)
])
}
Is there a good way to create some type of reuse functionality this is something I was playing around with based on This Answer although I realize that is primarily for UIPageViewControllers where allocation and deallocation of UIViewController's is handled for you.
Possible Reuse Functionality
var reuseableViewControllers:[UIViewController] = [UIViewController]()
private func unusedViewController() -> UIViewController {
let unusedViewControllers = reuseableViewControllers.filter { $0.parent == nil }
if let someUnusedViewController = unusedViewControllers.first {
return someUnusedViewController
} else {
let newViewController = UIViewController()
reuseableViewControllers.append(newViewController)
return newViewController
}
}

Resources