I am trying to add to my code a loop page . When the scroll view get to the last page the next page should be the first and vice versa.
In my project I have a main storyboard with a view controller where a Scroll View is placed and two xib file containing the two pages.
Everything works as should be however I cannot figure out a way how to have an infinite scrolling in both ways (back and forward)
If I add the function scrollViewDidScroll it seems that its not called.
Any Idea?
This is my code:
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Set Scroll Bar Hidden
scrollView.showsHorizontalScrollIndicator = false;
scrollView.showsVerticalScrollIndicator = false
// Create the views used in the swipe container view
let NewsPage :NewsViewController = NewsViewController(nibName: "NewsViewController", bundle: nil);
let MusicPage :MusicViewController = MusicViewController(nibName: "MusicViewController", bundle: nil);
// Add in each view to the container view hierarchy
self.addChildViewController(MusicPage);
self.scrollView!.addSubview(MusicPage.view);
MusicPage.didMoveToParentViewController(self);
self.addChildViewController(NewsPage);
self.scrollView!.addSubview(NewsPage.view);
NewsPage.didMoveToParentViewController(self);
// Set up the frames of the view controllers to align with eachother inside the container view
var adminFrame :CGRect = NewsPage.view.frame;
adminFrame.origin.x = adminFrame.width;
MusicPage.view.frame = adminFrame;
// Set the size of the scroll view that contains the frames
let scrollWidth: CGFloat = 2 * self.view.frame.width
let scrollHeight: CGFloat = self.view.frame.size.height
self.scrollView!.contentSize = CGSizeMake(scrollWidth, scrollHeight);
NSLog("%f",scrollHeight);
}
func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView.contentOffset.x > scrollView.frame.size.width){
scrollView.setContentOffset(CGPointMake(0.0,0.0), animated: false)
}
}
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
NSLog("dragg");
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Did you set the scrollView's delegate? Try adding to viewDidLaod
scrollView.delegate = self
Related
I’m new in Swift and I tried making UIScrollView that shows view controllers.
Every thing perfect just at iPhone 11 Pro Max the next screen show a little bit on the side:
the orange strip is the next screen
My Code:
//MARK: - outlets
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var scrollView: UIScrollView!
//MARK: - properties
var viewControllers: [String] = ["ComputerViewController", "AttactViewController", "DefenceViewController", "OfflineViewController"]
var frame = CGRect(x: 0, y: 0, width: 0, height: 0)
//MARK: - life cyrcles
override func viewDidLoad() {
super.viewDidLoad()
for index in 0..<viewControllers.count {
frame.origin.x = scrollView.frame.size.width * CGFloat(index)
frame.size = scrollView.frame.size
let view = UIView(frame: frame)
let storyboard = UIStoryboard(name: "Menu", bundle: nil)
var controller: UIViewController = storyboard.instantiateViewController(withIdentifier: viewControllers[index]) as UIViewController
view.addSubview(controller.view)
self.scrollView.addSubview(view)
}
scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(viewControllers.count), height: scrollView.frame.size.height)
scrollView.delegate = self
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
pageControl.currentPage = Int(pageNumber)
}
thanks for helping...
A couple of observations:
You should avoid referencing frame in viewDidLoad. At this point, the frame is not known.
You should avoid hard-coding the size and placement of the subviews at all. There can be a variety of events that change the view’s size later on (e.g. rotations, split view multitasking, etc.). Use constraints.
With scroll views, there are two layout guides, one for its frame and another for its contentSize. So set the size of the subviews using the frameLayoutGuide and the placement of these subviews relative to the contentLayoutGuide.
When you add a view controller’s view as a subview, make sure you call addChild(_:) and didMove(toParent:) calls for view controller containment. See “Implementing a Container View Controller” section of the view controller documentation.
If you want to add paging behavior, just set isPagingEnabled of the scroll view.
Pulling that all together:
override func viewDidLoad() {
super.viewDidLoad()
addChildViews()
}
override func viewDidLoad() {
super.viewDidLoad()
var anchor = scrollView.contentLayoutGuide.leadingAnchor
for identifier in viewControllers {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let child = storyboard.instantiateViewController(withIdentifier: identifier)
addChild(child) // containment call
child.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(child.view)
child.didMove(toParent: self) // containment call
NSLayoutConstraint.activate([
// define size of child view (relative to `frameLayoutGuide`)
child.view.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor),
child.view.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor),
// define placement of child view (relative to `contentLayoutGuide`)
child.view.leadingAnchor.constraint(equalTo: anchor),
child.view.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
child.view.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
])
anchor = child.view.trailingAnchor
}
anchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor).isActive = true
scrollView.isPagingEnabled = true
}
I’ve eliminated the property frame as that’s not needed anymore (and is just a source of confusion with the view controller’s view’s property of the same name). I’ve also eliminated the container view as it didn’t add much to the overall solution (and only adds a layer of constraints to add).
But the key is to use constraints to dictate the size and position of the subviews within the scroll view and to use view controller containment API.
I'm trying to follow the example described here for making a stretchy layout which includes a UIImageView and UIScrollView. https://github.com/TwoLivesLeft/StretchyLayout/tree/Step-6
The only difference is that I replace the UILabel used in the example with the view of a child UIViewController which itself contains a UICollectionView. This is how my layout looks - the blue items are the UICollectionViewCell.
This is my code:
import UIKit
import SnapKit
class HomeController: UIViewController, UIScrollViewDelegate {
private let scrollView = UIScrollView()
private let imageView = UIImageView()
private let contentContainer = UIView()
private let collectionViewController = CollectionViewController()
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad() {
super.viewDidLoad()
scrollView.contentInsetAdjustmentBehavior = .never
scrollView.delegate = self
imageView.image = UIImage(named: "burger")
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
let imageContainer = UIView()
imageContainer.backgroundColor = .darkGray
contentContainer.backgroundColor = .clear
let textBacking = UIView()
textBacking.backgroundColor = #colorLiteral(red: 0.7450980544, green: 0.1235740449, blue: 0.2699040081, alpha: 1)
view.addSubview(scrollView)
scrollView.addSubview(imageContainer)
scrollView.addSubview(textBacking)
scrollView.addSubview(contentContainer)
scrollView.addSubview(imageView)
self.addChild(collectionViewController)
contentContainer.addSubview(collectionViewController.view)
collectionViewController.didMove(toParent: self)
scrollView.snp.makeConstraints {
make in
make.edges.equalTo(view)
}
imageContainer.snp.makeConstraints {
make in
make.top.equalTo(scrollView)
make.left.right.equalTo(view)
make.height.equalTo(imageContainer.snp.width).multipliedBy(0.7)
}
imageView.snp.makeConstraints {
make in
make.left.right.equalTo(imageContainer)
//** Note the priorities
make.top.equalTo(view).priority(.high)
//** We add a height constraint too
make.height.greaterThanOrEqualTo(imageContainer.snp.height).priority(.required)
//** And keep the bottom constraint
make.bottom.equalTo(imageContainer.snp.bottom)
}
contentContainer.snp.makeConstraints {
make in
make.top.equalTo(imageContainer.snp.bottom)
make.left.right.equalTo(view)
make.bottom.equalTo(scrollView)
}
textBacking.snp.makeConstraints {
make in
make.left.right.equalTo(view)
make.top.equalTo(contentContainer)
make.bottom.equalTo(view)
}
collectionViewController.view.snp.makeConstraints {
make in
make.left.right.equalTo(view)
make.top.equalTo(contentContainer)
make.bottom.equalTo(view)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.scrollIndicatorInsets = view.safeAreaInsets
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: view.safeAreaInsets.bottom, right: 0)
}
//MARK: - Scroll View Delegate
private var previousStatusBarHidden = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if previousStatusBarHidden != shouldHideStatusBar {
UIView.animate(withDuration: 0.2, animations: {
self.setNeedsStatusBarAppearanceUpdate()
})
previousStatusBarHidden = shouldHideStatusBar
}
}
//MARK: - Status Bar Appearance
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
override var prefersStatusBarHidden: Bool {
return shouldHideStatusBar
}
private var shouldHideStatusBar: Bool {
let frame = contentContainer.convert(contentContainer.bounds, to: nil)
return frame.minY < view.safeAreaInsets.top
}
}
Everything is the same as in this file: https://github.com/TwoLivesLeft/StretchyLayout/blob/Step-6/StretchyLayouts/StretchyViewController.swift with the exception of the innerText being replaced by my CollectionViewController.
As you can see, the UICollectionView is displayed properly - however I am unable to scroll up or down anymore. I'm not sure where my mistake is.
It looks like you are constraining the size of your collection view to fit within the bounds of the parent view containing the collection view's container view and the image view. As a result, the container scrollView has no contentSize to scroll over, and that's why you can't scroll. You need to ensure your collection view's content size is reflected in the parent scroll view's content size.
In the example you gave, this behavior was achieved by the length of the label requiring a height greater than the height between the image view and the rest of the view. In your case, the collection view container needs to behave as if it's larger than that area.
Edit: More precisely you need to pass the collectionView.contentSize up to your scrollView.contentSize. A scrollview's contentSize is settable, so you just need to increase the scrollView.contentSize by the collectionView.contentSize - collectionView.height (since your scrollView's current contentSize currently includes the collectionView's height). I'm not sure how you are adding your child view controller, but at the point you do that, I would increment your scrollView's contentSize accordingly. If your collectionView's size changes after that, though, you'll also need to ensure you delegate that change up to your scrollView. This could be accomplished by having a protocol such as:
protocol InnerCollectionViewHeightUpdated {
func collectionViewContentHeightChanged(newSize: CGSize)
}
and then making the controller containing the scrollView implement this protocol and update the scrollView contentSize accordingly. From your collectionView child controller, you would have a delegate property for this protocol (set this when creating the child view controller, setting the delegate as self, the controller containing the child VC and also the scrollView). Then whenever the collectionView height changes (if you add cells, for example) you can do delegate.collectionViewContentHeightChanged(... to ensure your scroll behavior will continue to function.
I have a view in which i am creating another view programmatically on add button through xib. I am able to create multiple views on tapping add more button and remove is also working if I remove last view but the problem occurs with middle views due to missing constraints view not updating correctly bellow are images how it is looking
At start view Look like this
After Adding more view
After removing middle view
Delete button code
#IBAction func deletebnt(_ sender: UIButton) {
let view = self.superview
let index = view?.subviews.index(of:self)!
delegate.txtcheck(text: countstr)
self.view.removeFromSuperview()
}
Add button Code
#IBAction func addMoreBnt(_ sender: UIButton) {
for constraint in addSuperview.constraints {
if constraint.firstAttribute == NSLayoutAttribute.height
{
constraint.constant += 45
space = constraint.constant
}
}
let newView : AvalabileTimeView = AvalabileTimeView()
newView.frame = CGRect(x: self.addsubView.frame.origin.x, y: 70, width: addsubView.frame.size.width, height:addsubView.frame.size.height)
newView.delegate = self as AvalabileTimeDelegate
addSuperview.addSubview(newView)
let index = addSuperview.subviews.index(of: newView)!
newView.translatesAutoresizingMaskIntoConstraints = false
let heightConstraint = newView.widthAnchor.constraint(equalToConstant:addsubView.frame.size.width )
let widthConstaint = newView.heightAnchor.constraint(equalToConstant:31 )
let topConstraint = newView.topAnchor.constraint(equalTo: addSuperview.topAnchor, constant: space - 31) NSLayoutConstraint.activate([heightConstraint,topConstraint,widthConstaint])
}
delegate to change height of superview
func txtcheck(text: String!) {
print(text)
for constraint in addSuperview.constraints {
if constraint.firstAttribute == NSLayoutAttribute.height
{
constraint.constant -= 45
// Here I have to set constraint for bottom view and topview of deleted view but I don't know how to do
}
}
}
Here is link to demo project
https://github.com/logictrix/addFieldDemo
Instead of adding each new AvalabileTimeView with its own constraints, use a UIStackView - you can remove almost all of your existing code for adding / removing the new views.
Look at .addArrangedSubview() and .removeArrangedSubview()
Here is some sample code... you'll need to add a UIStackView in Interface Builder, connect it to the Outlet, and adjust the constraints, but that's about all:
// in ViewController.swift
#IBOutlet weak var availableTimeStackView: UIStackView!
#IBAction func addMoreBnt(_ sender: UIButton) {
// instantiate a new AvalabileTimeView
let newView : AvalabileTimeView = AvalabileTimeView()
// set its delegate to self
newView.delegate = self as AvalabileTimeDelegate
// add it to the Stack View
availableTimeStackView.addArrangedSubview(newView)
// standard for auto-layout
newView.translatesAutoresizingMaskIntoConstraints = false
// only constraint needed is Height (width and vertical spacing handled by the Stack View)
newView.heightAnchor.constraint(equalToConstant: 31).isActive = true
}
// new delegate func
func removeMe(_ view: AvalabileTimeView) {
// remove the AvalabileTimeView from the Stack View
availableTimeStackView.removeArrangedSubview(view)
}
// in AvalabileTimeView.swift
protocol AvalabileTimeDelegate{
// don't need this anymore
func txtcheck(text: String!)
// new delegate func
func removeMe(_ view: AvalabileTimeView)
}
#IBAction func deletebnt(_ sender: UIButton) {
delegate.removeMe(self)
}
I have created a UIStackView in IB which has the distribution set to Fill Equally. I am looking to get the frame for each subView but the following code always returns (0, 0, 0, 0).
class ViewController: UIViewController {
#IBOutlet weak var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
let pView = UIView()
let sView = UIView()
pView.backgroundColor = UIColor.red
sView.backgroundColor = UIColor.orange
stackView.addArrangedSubview(pView)
stackView.addArrangedSubview(sView)
}
override func viewDidLayoutSubviews() {
print(stackView.arrangedSubviews[0].frame)
print(stackView.arrangedSubviews[1].frame)
}
}
I would think that a stack view set to fill equally would automatically set the calculate it.
Any help would be appreciated.
After reading over your code I think this is just a misunderstanding of viewDidLayoutSubviews(). Basically it is called when all the views that are descendants of the main view have been laid out but this does not include the subviews(descendants) of these views. See discussion notes from Apple.
"When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method. However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted. Each subview is responsible for adjusting its own layout."
Now there are many ways to get the frame of the subviews with this being said.
First you could add one line of code in viewdidload and get it there.
override func viewDidLoad() {
super.viewDidLoad()
let pView = UIView()
let sView = UIView()
pView.backgroundColor = UIColor.red
sView.backgroundColor = UIColor.orange
stackView.addArrangedSubview(pView)
stackView.addArrangedSubview(sView)
stackView.layoutIfNeeded()
print(stackView.arrangedSubviews[0].frame)
print(stackView.arrangedSubviews[1].frame)
}
OR you can wait until viewDidAppear and check there.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(stackView.arrangedSubviews[0].frame)
print(stackView.arrangedSubviews[1].frame)
}
i'm trying to keep the UISearchBarDisplayController on top of UItableview in swift
i tried this code (which i convert from objectC to swift) but no luck:
override func scrollViewDidScroll(scrollView: UIScrollView) {
//
var searchBar: UISearchBar = (self.searchDisplayController?.searchBar)!
//
var rect = searchBar.frame
self.searchDisplayController?.searchBar.frame = CGRectMake(0, max(0, scrollView.contentOffset.y), rect.width, rect.height);
}
the search bar still scroll with tableview when i scroll the table. Does anyone can help me to fix it? thanks
i found that i have to make a new view controller , add a tableview and add a searchBar outside of the tableview. and the search bar won't scroll with the tableview
In Swift 2.1, iOS 9.2.1 and Xcode 7.2
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
/* Search controller parameters */
searchController.searchResultsUpdater = self // This protocol allows your class to be informed as text changes within the UISearchBar.
searchController.dimsBackgroundDuringPresentation = false // In this instance,using current view to show the results, so do not want to dim current view.
definesPresentationContext = true // ensure that the search bar does not remain on the screen if the user navigates to another view controller while the UISearchController is active.
let tableHeaderView: UIView = UIView.init(frame: searchController.searchBar.frame)
tableHeaderView.addSubview(searchController.searchBar)
self.tableView.tableHeaderView = tableHeaderView
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
let searchBar:UISearchBar = searchController.searchBar
var searchBarFrame:CGRect = searchBar.frame
if searchController.active {
searchBarFrame.origin.y = 10
}
else {
searchBarFrame.origin.y = max(0, scrollView.contentOffset.y + scrollView.contentInset.top)
}
searchController.searchBar.frame = searchBarFrame
}