I need to create a paging ScrollView which shows a sequence of images.
I created a ScrollView in the main view of the Storyboard and set this constraints (to center the ScrollView in the view):
Constraint
Then I activated paging and disabled the "Content layout guides" option.
Next, in the view class I set up the UIScrollViewDelegate delegate and I wrote the following code to show 3 images (they are 3 colored squares):
class ViewController: UIViewController, UIScrollViewDelegate {
// Outlet
#IBOutlet weak var scrollview: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollview.delegate = self;
let infoArray = ["01", "02", "03"];
for i in 0..<infoArray.count {
let imageView = UIImageView();
imageView.contentMode = .scaleToFill;
imageView.image = UIImage(named: infoArray[i]);
let xPos = CGFloat(i) * scrollview.bounds.size.width;
imageView.frame = CGRect(x: xPos, y: 0, width: scrollview.bounds.size.width, height: scrollview.bounds.size.height);
imageView.layer.borderWidth = 1;
scrollview.contentSize.width = scrollview.frame.size.width * CGFloat(i+1);
scrollview.contentSize.height = scrollview.frame.size.height;
scrollview.addSubview(imageView);
}
scrollview.layer.borderWidth = 1;
}
}
I have set that the images must have the same width and height as the scrollview. But these are larger in the simulator (and in my iPhone 11) and therefore the display is incorrect. I show you the sequence of the 3 squares:
Page 1
Page 2
Page 3
Page 4
I can't understand where I'm wrong. Why don't the 3 images take the size of the scrollview?
Why are there 4 pages?
Thanks for your help
Okay, here is how you do it:
Your scrollview is created in storyboard and its layout is set. Make sure content layout guides is unchecked in the size inspector and paging is checked in the attribute inspector.
Add a stackview as a subview to your scrollview (this will act as the content view). Pin your stackView to all 4 edges of the scrollView.
Set Height and Width Equal to the scrollView height and width. Set the Width priority to 250. (that indicates that the scrollview will scroll horizontally)
Set the stackView to horizontal axis, fill alignment and fillEqually distribution.
Now, go back to viewDidLoad and add the following code below. ScrollViewContentView is the stackView that acts as a contentView for the scrollView. Note that since stackView is set to fillEqually, you only need set one of the image's width constraint.
scrollViewContentView.addArrangedSubview(image1)
scrollViewContentView.addArrangedSubview(image2)
scrollViewContentView.addArrangedSubview(image3)
image1.translatesAutoresizingMaskIntoConstraints = false
image2.translatesAutoresizingMaskIntoConstraints = false
image3.translatesAutoresizingMaskIntoConstraints = false
image1.backgroundColor = .blue
image2.backgroundColor = .yellow
image3.backgroundColor = .red
image1.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
I think you need to state the frame of the scrollview before you declare the imageViews x positions or widths.
class ViewController: UIViewController, UIScrollViewDelegate {
// Outlet
#IBOutlet weak var scrollview: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollview.frame = view.frame // declared here
scrollview.delegate = self;
let infoArray = ["01", "02", "03"];
for i in 0..<infoArray.count {
let imageView = UIImageView();
imageView.contentMode = .scaleToFill;
imageView.image = UIImage(named: infoArray[i]);
let xPos = CGFloat(i) * scrollview.bounds.size.width;
imageView.frame = CGRect(x: xPos, y: 0, width: scrollview.bounds.size.width, height: scrollview.bounds.size.height);
imageView.layer.borderWidth = 1;
scrollview.contentSize.width = scrollview.frame.size.width * CGFloat(i+1);
scrollview.contentSize.height = scrollview.frame.size.height;
scrollview.addSubview(imageView);
}
scrollview.layer.borderWidth = 1;
}
}
Related
I have a scroll view and an imageView at the top, I want to pin and make a stretchable image view but if I add this image view to the view the image won't disappear when the user scroll down the view and if I add the image to the scroll view this won't be pinned at the top when the user scroll down.
So how can I pin the image at the top and then when the user scroll down the image will disappear.
Like that: http://blog.enabled.com.au/stretchy-layouts-on-ios/ but not with his framework.
class LocalsVC: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
setNavBarSettings()
scrollView.delegate = self
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
view.addSubview(imageView)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let y = 300 - (scrollView.contentOffset.y + 300)
let height = min(max(y, 60), 400)
imageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: height)
}
Add the image to the scrollView and not to the view: scrollView.addSubview(imageView).
I'm trying to show simple custom view into scrollView. Here's my code :
struct scrollViewDataStruct {
let title: String?
let image: UIImage?
}
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
var scrollViewData = [scrollViewDataStruct]()
override func viewDidLoad() {
super.viewDidLoad()
scrollViewData = [
scrollViewDataStruct(title: "First", image: #imageLiteral(resourceName: "iPhone 8 Copy 2")),
scrollViewDataStruct(title: "Second", image: #imageLiteral(resourceName: "iPhone 8 Copy 3"))
]
self.scrollView.backgroundColor = .yellow
scrollView.contentSize.width = self.scrollView.frame.width * CGFloat(scrollViewData.count)
var i = 0
for _ in scrollViewData {
let view = CustomView(frame: CGRect(x: self.scrollView.frame.width * CGFloat(i), y: 0, width: scrollView.frame.width, height: self.scrollView.frame.height))
self.scrollView.addSubview(view)
i += 1
}
// Do any additional setup after loading the view, typically from a nib.
}
}
class CustomView: UIView {
let imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.backgroundColor = UIColor.blue
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(imageView)
imageView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
As you can see, the CustomView's frame = scrollView's frame but when i ran application it's not as I expected :
Then, in storyboard, i change device from iphone8 to iphone 8 plus and run again. It's show CustomView correctly. I have no idea, the scrollView is always correct but the CustomView is not .
Any suggest ?
Your problem is that you are accessing the frame of the scrollView before Auto Layout has run and established the size of the frame for the actual device. A quick fix is to move your setup code into an override of viewDidLayoutSubviews.
You have to be careful though, because unlike viewDidLoad, viewDidLayoutSubviews will run more than once, so you have to make sure you don't add your views multiple times.
// property - have we set up the views yet?
var setup = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if !setup {
scrollView.contentSize.width = self.scrollView.frame.width * CGFloat(scrollViewData.count)
var i = 0
for _ in scrollViewData {
let view = CustomView(frame: CGRect(x: self.scrollView.frame.width * CGFloat(i), y: 0, width: scrollView.frame.width, height: self.scrollView.frame.height))
self.scrollView.addSubview(view)
i += 1
}
setup = true
}
}
You should consider using constraints to place your views within your scrollView content instead of messing with the frame calculations, then Auto Layout would just automatically do the right thing.
In viewDidLoad, UI component will suppose to have the size you have taken in storyboard.
There are 2 ways to do this:
1. Use autoresizingMask property
autoresizingMask property will resize the view, if its containerView's frames changed
var i = 0
for _ in scrollViewData {
let view = CustomView(frame: CGRect(x: self.scrollView.frame.width * CGFloat(i), y: 0, width: scrollView.frame.width, height: self.scrollView.frame.height))
view.autoresizingMask = .flexibleHeight
self.scrollView.addSubview(view)
i += 1
}
2. Use fixed parameters, say UIScreen.main.bounds.size.height
Just update your code for custom view's height with reference to screen height rather than scroll view's height. It will work fine
var i = 0
let height = UIScreen.main.bounds.size.height
for _ in scrollViewData {
let view = CustomView(frame: CGRect(x: self.scrollView.frame.width * CGFloat(i), y: 0, width: scrollView.frame.width, height: height))
self.scrollView.addSubview(view)
i += 1
}
I have a Swift code
background_img_view.addSubview(background_image)
background_img_view.addSubview(usn)
profile_view.addSubview(PostsDiv)
profile_view.addSubview(PostDiv)
var scrollView: UIScrollView!
scrollView = UIScrollView(frame: view.bounds)
scrollView.contentSize = CGSize(width: self.view.frame.size.width, height: 1500)
scrollView.autoresizingMask = UIViewAutoresizing.flexibleHeight
scrollView.isScrollEnabled = true
scrollView.addSubview(profile_view)
view.addSubview(background_img_view)
view.addSubview(scrollView)
I tested this on my phone and scroll bar is showing, but when i scroll nothing changes, also i can't click on anything on the screen .
Full code http://pastebin.com/EXQeGMJ6
I would like to create a nav bar similar to what's in the image that's attached.
The title of the nav bar will be a combination of an image and text.
Should this be done per any best practice?
How can it be done?
As this answer shows, the easiest solution is to add the text to your image and add that image to the navigation bar like so:
var image = UIImage(named: "logo.png")
self.navigationItem.titleView = UIImageView(image: image)
But if you have to add text and an image separately (for example, in the case of localization), you can set your navigation bar's title view to contain both image and text by adding them to a UIView and setting the navigationItem's title view to that UIView, for example (assuming the navigation bar is part of a navigation controller):
// Only execute the code if there's a navigation controller
if self.navigationController == nil {
return
}
// Create a navView to add to the navigation bar
let navView = UIView()
// Create the label
let label = UILabel()
label.text = "Text"
label.sizeToFit()
label.center = navView.center
label.textAlignment = NSTextAlignment.Center
// Create the image view
let image = UIImageView()
image.image = UIImage(named: "Image.png")
// To maintain the image's aspect ratio:
let imageAspect = image.image!.size.width/image.image!.size.height
// Setting the image frame so that it's immediately before the text:
image.frame = CGRect(x: label.frame.origin.x-label.frame.size.height*imageAspect, y: label.frame.origin.y, width: label.frame.size.height*imageAspect, height: label.frame.size.height)
image.contentMode = UIViewContentMode.ScaleAspectFit
// Add both the label and image view to the navView
navView.addSubview(label)
navView.addSubview(image)
// Set the navigation bar's navigation item's titleView to the navView
self.navigationItem.titleView = navView
// Set the navView's frame to fit within the titleView
navView.sizeToFit()
Use horizontal UIStackView should be much cleaner and easier
Please add the next extension to UIViewController
extension UIViewController {
func setTitle(_ title: String, andImage image: UIImage) {
let titleLbl = UILabel()
titleLbl.text = title
titleLbl.textColor = UIColor.white
titleLbl.font = UIFont.systemFont(ofSize: 20.0, weight: .bold)
let imageView = UIImageView(image: image)
let titleView = UIStackView(arrangedSubviews: [imageView, titleLbl])
titleView.axis = .horizontal
titleView.spacing = 10.0
navigationItem.titleView = titleView
}
}
then use it inside your viewController:
setTitle("yourTitle", andImage: UIImage(named: "yourImage"))
(this will align the text and the icon together to the center, if you want the text to be centered and the icon in the left, just add an empty UIView with width constraint equal to the icon width)
here is my 2 cents for Swift 4, since accepted answer didn't work for me (was mostly off the screen):
// .. in ViewController
var navBar = CustomTitleView()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// =================== navBar =====================
navBar.loadWith(title: "Budget Overview", leftImage: Images.pie_chart)
self.navigationItem.titleView = navBar
}
class CustomTitleView: UIView
{
var title_label = CustomLabel()
var left_imageView = UIImageView()
override init(frame: CGRect){
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
setup()
}
func setup(){
self.addSubview(title_label)
self.addSubview(left_imageView)
}
func loadWith(title: String, leftImage: UIImage?)
{
//self.backgroundColor = .yellow
// =================== title_label ==================
//title_label.backgroundColor = .blue
title_label.text = title
title_label.font = UIFont.systemFont(ofSize: FontManager.fontSize + 5)
// =================== imageView ===================
left_imageView.image = leftImage
setupFrames()
}
func setupFrames()
{
let height: CGFloat = Navigation.topViewController()?.navigationController?.navigationBar.frame.height ?? 44
let image_size: CGFloat = height * 0.8
left_imageView.frame = CGRect(x: 0,
y: (height - image_size) / 2,
width: (left_imageView.image == nil) ? 0 : image_size,
height: image_size)
let titleWidth: CGFloat = title_label.intrinsicContentSize.width + 10
title_label.frame = CGRect(x: left_imageView.frame.maxX + 5,
y: 0,
width: titleWidth,
height: height)
contentWidth = Int(left_imageView.frame.width)
self.frame = CGRect(x: 0, y: 0, width: CGFloat(contentWidth), height: height)
}
var contentWidth: Int = 0 //if its CGFloat, it infinitely calls layoutSubviews(), changing franction of a width
override func layoutSubviews() {
super.layoutSubviews()
self.frame.size.width = CGFloat(contentWidth)
}
}
Swift 4.2 + Interface Builder Solution
As a follow-on to Lyndsey Scott's answer, you can also create a UIView .xib in Interface Builder, use that to lay out your title and image, and then update it on-the-fly via an #IBOutlet. This is useful for dynamic content, internationalization, maintainability etc.
Create a UIView subclass with a UILabel outlet and assign your new .xib to this class:
import UIKit
class FolderTitleView: UIView {
#IBOutlet weak var title : UILabel!
/// Create an instance of the class from its .xib
class func instanceFromNib() -> FolderTitleView {
return UINib(nibName: "FolderTitleView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! FolderTitleView
}
}
Connect the label to your outlet (title in my example) in your .xib, then in your UIViewController:
/// Reference to the title view
var folderTitleView : FolderTitleView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Set the screen title to match the active folder
updateTitle()
}
/// Updates the title of the navigation controller.
func updateTitle() {
self.title = ""
if folderTitleView == nil {
folderTitleView = FolderTitleView.instanceFromNib()
self.navigationItem.titleView = folderTitleView
}
folderTitleView!.title.text = "Listening"
folderTitleView!.layoutIfNeeded()
}
This results in a nice self-centering title bar with an embedded image that you can easily update from code.
// worked for me
create a view and set the frame
now add the image in the view and set the frame
after adding the image, add the label in same view and set the frame
after adding the image and label to view, add same view to navigationItem
let navigationView = UIView(frame: CGRect(x: 0, y: 0, width: 50 , height: 55))
let labell : UILabel = UILabel(frame: CGRect(x: -38, y: 25, width: 150, height: 25))
labell.text = "Your text"
labell.textColor = UIColor.black
labell.font = UIFont.boldSystemFont(ofSize: 10)
navigationView.addSubview(labell)
let image : UIImage = UIImage(named: ValidationMessage.headerLogoName)!
let imageView = UIImageView(frame: CGRect(x: -20, y: 0, width: 100, height: 30))
imageView.contentMode = .scaleAspectFit
imageView.image = image
//navigationItem.titleView = imageView
navigationView.addSubview(imageView)
navigationItem.titleView = navigationView
I have a view controller that has a UIView(ParentView) and then UIScrollview(ScrollView). The ParentView is anchored to the leading, trailing, top and bottom of the ViewController. The ScrollView is anchored to the leading, trailing, top and bottom of the ParentView. The structure is like this:
-UIView
--ParentView
---ScrollView
Then I created a xib file that has an image and a label. The xib will be added dynamically to the ScrollView. The xib frame height and width are equal to the ScrollView height and width.
When I run the simulator on iphone 6 plus the scroll works perfectly; the width of the xib is exactly the width of the screen. But when i run the simulator with iphone 6, the width of the xib is the same size of the screen, there is an extra space when scrolling to the next item. What should I do to removed it!?
This is a screen shot for iphone 6 plus
This is a screen shot for iphone 6. Notice the extra space of the second screen
And here is my code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var ParentView: UIView!
#IBOutlet weak var ScrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let photos = ["joe", "john"]
let userNames = ["joe", "john"]
self.ScrollView.frame = CGRectMake(0, 0, self.ParentView.frame.size.width, self.ParentView.frame.size.height)
self.ScrollView.contentSize = CGSizeMake(self.ParentView.frame.size.width * CGFloat(photos.count), self.ParentView.frame.size.height)
self.ScrollView.pagingEnabled = true
var i: Int = 0
while i < 2 {
if let userXib = NSBundle.mainBundle().loadNibNamed("User", owner: self, options: nil)[0] as? User {
userXib.frame = CGRect(x: self.ScrollView.frame.size.width * CGFloat(i), y: 0, width: self.ScrollView.frame.size.width, height: userXib.frame.size.height)
userXib.assignValues(photos[i], myName: userNames[i])
self.ScrollView.addSubview(userXib)
}
i = i + 1
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
UPDATE #1 - Coder1000 solution:
I tried implementing #3 & #4 in your answer by adding the UIView layer inside the scroll. I called it ScrollMainView. In the storyboard, I set its leading, trailing, top and bottom to fill its superview: Scrollview and then in the loop added those elements to it instead of the scrollview. But now the scroll doesn't scroll! My code and final display look like this:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var ParentView: UIView!
#IBOutlet weak var ScrollView: UIScrollView!
#IBOutlet weak var ScrollMainView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let photos = ["joe", "john"]
let userNames = ["joe", "john"]
self.ScrollView.frame = CGRectMake(0, 0, self.ParentView.frame.size.width, self.ParentView.frame.size.height)
self.ScrollView.contentSize = CGSizeMake(self.ParentView.frame.size.width * CGFloat(photos.count), self.ParentView.frame.size.height)
self.ScrollView.pagingEnabled = true
self.ScrollMainView.frame = CGRectMake(0, 0, self.ScrollView.frame.size.width, self.ScrollView.frame.size.height)
print("the frame for parentview is: \(self.ParentView.frame)")
var i: Int = 0
while i < 2 {
if let userXib = NSBundle.mainBundle().loadNibNamed("User", owner: self, options: nil)[0] as? User {
userXib.frame = CGRect(x: self.ScrollMainView.frame.size.width * CGFloat(i), y: 0, width: self.ScrollMainView.frame.size.width, height: userXib.frame.size.height)
userXib.assignValues(photos[i], myName: userNames[i])
self.ScrollMainView.addSubview(userXib)
}
i = i + 1
}
self.ScrollView.addSubview(ScrollMainView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
There are several solutions, in viewDidLoad the frame is not set correctly, so when you ask it, it is wrong.
The parentView and your scrollView are adjusted by the autolayout constraint later, but you User view is not...
A simple solution may be to put the code in:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.ScrollView.frame = CGRectMake(0, 0, self.ParentView.frame.size.width, self.ParentView.frame.size.height)
self.ScrollView.contentSize = CGSizeMake(self.ParentView.frame.size.width * CGFloat(photos.count), self.ParentView.frame.size.height)
self.ScrollView.pagingEnabled = true
for subView: UIView in self.ScrollView.subviews {
if subView is User { subView.removeFromSuperview()
}
var i: Int = 0
while i < 2 {
if let userXib = NSBundle.mainBundle().loadNibNamed("User", owner: self, options: nil)[0] as? User {
userXib.frame = CGRect(x: self.ScrollView.frame.size.width * CGFloat(i), y: 0, width: self.ScrollView.frame.size.width, height: userXib.frame.size.height)
userXib.assignValues(photos[i], myName: userNames[i])
self.ScrollView.addSubview(userXib)
}
i = i + 1
}
}
Or you can add constraints to your custom xib programmatically.
Or you can keep an array of your costom xibs and just adjust their frames, AND THE SCROLLVIEW CONTENTSIZE, in viewDidLayoutSubviews()
Or you can put the code in viewDidAppear()