I am able to present the PDFThumnailView of my pdf document. The problem is it is skipping pages. It shows pages 1,3,5 etc...and not displaying the pages in between. My code is below.
#IBOutlet weak var pdfView: PDFView!
#IBOutlet weak var pdfThumbnailView: PDFThumbnailView!
func setupThumbnailView() {
pdfThumbnailView.pdfView = pdfView
pdfThumbnailView.thumbnailSize = CGSize.init(width: thumbnailDimension, height: thumbnailDimension)
pdfThumbnailView.backgroundColor = sidebarBackgroundColor
}
I might be wrong but I think this is the way that PDFThumbnailView works. It fits as many thumbnails as it can within the View width by not displaying some intermediate thumbnails. It does show them if you touch over the thumbnails. Make the width bigger or the thumbnails smaller and it shows more.
You have to make pdfThumbnailView bigger. However then it can be too big to fit on the screen, so let’s put it in a scroll view.
First make change the constraints on the pdfThumbnailView to make it wide enough to accomodate all the pages.
NSLayoutConstraint.activate([
pdfThumbnailView.heightAnchor.constraint(equalToConstant: CGFloat(thumbnailSize)),
pdfThumbnailView.widthAnchor.constraint(equalToConstant: CGFloat(pdfDocument.pageCount*thumbnailSize))
])
Next create the scroll view and add the thumbnail view as its only subview.
var pdfThumbnailScrollView = UIScrollView()
pdfThumbnailScrollView.translatesAutoresizingMaskIntoConstraints = false
pdfThumbnailScrollView.addSubview(pdfThumbnailView)
Finally add some constraints so the scroll view and the thumbnail view know how to lay themselves out. The scroll view only has one subview, so let's constrain it to fit.
NSLayoutConstraint.activate([
pdfThumbnailView.leadingAnchor.constraint(equalTo: pdfThumbnailScrollView.leadingAnchor),
pdfThumbnailView.trailingAnchor.constraint(equalTo: pdfThumbnailScrollView.trailingAnchor),
pdfThumbnailView.topAnchor.constraint(equalTo: pdfThumbnailScrollView.topAnchor),
pdfThumbnailView.bottomAnchor.constraint(equalTo: pdfThumbnailScrollView.bottomAnchor)
])
You may follow this tutorial more details.
Related
(Xcode 11, Swift)
Being a newbie to iOS and Autolayout, I'm struggling with implementing a fairly simple (IMHO) view which displays a [vertical] list of items. The only problem is that items are decided dynamically and each of them could be either text or image (where either of those could be fairly large so scrolling would be required). WebView is not an option, so it has to be implemented natively.
This is how I understand the process:
Make in IB a UIScrollView and size it to the size of the outer frame.
Make a container view as a subview of UIScrollView (again, in IB) and size it the same.
Set constraint on equal width of both
At runtime, populate container view with UILabels/UIImageViews and also set constraints programmatically to ensure proper layout.
"Tell" scrollview about the subview height in order to make it manage the scrolling thereof.
Is this the right approach? It doesn't seem to work for me (for a toy example of dynamically adding a very tall image to a container view - I cannot get the scrolling to work). What would be the proper way to do the last step in the process above - just force the contentSize of the scrollview to the size of the populated container view (it doesn't seem to work for me). Any help would be appreciated.
When adding multiple elements to a scroll view at run-time, you may find it much easier to use a UIStackView... when setup properly, it will automatically grow in height with each added object.
As a simple example...
1) Start by adding a UIScrollView (I gave it a blue background to make it easier to see). Constrain it to Zero on all 4 sides:
Note that we see the "red circle" indicating missing / conflicting constraints. Ignore that for now.
2) Add a UIView as a "content view" to the scroll view (I gave it a systemYellow background to make it easier to see). Constrain it to Zero on all 4 sides to the Content Layout Guide -- this will (eventually) define the scroll view's content size. Also constrain it equal width and equal height to the Frame Layout Guide:
Important Step: Select the Height constraint, and in the Size Inspector pane select the Placeholder - Remove at build time checkbox. This will satisfy auto-layout in IB during design time, but will allow the height of that view to shrink / grow as necessary.
3) Add a Vertical UIStackView to the "content view". Constrain it to Zero on all 4 sides. Configure its properties to Fill / Fill / 8 (as shown below):
4) Add an #IBOutlet connection to the stack view in your view controller class. Now, at run-time, as you add UI elements to the stack view, all of your "scrollability" will be handled by auto-layout.
Here is an example class:
class DynaScrollViewController: UIViewController {
#IBOutlet var theStackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// local var so we can reuse it
var theLabel = UILabel()
var theImageView = UIImageView()
// create a new label
theLabel = UILabel()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theLabel.translatesAutoresizingMaskIntoConstraints = false
// multi-line
theLabel.numberOfLines = 0
// cyan background to make it easy to see
theLabel.backgroundColor = .cyan
// add 9 lines of text to the label
theLabel.text = (1...9).map({ "Line \($0)" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// add another label
theLabel = UILabel()
// multi-line
theLabel.numberOfLines = 0
// yellow background to make it easy to see
theLabel.backgroundColor = .yellow
// add 5 lines of text to the label
theLabel.text = (1...5).map({ "Line \($0)" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// create a new UIImageView
theImageView = UIImageView()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theImageView.translatesAutoresizingMaskIntoConstraints = false
// load an image for it - I have one named background
if let img = UIImage(named: "background") {
theImageView.image = img
}
// let's give the image view a 4:3 width:height ratio
theImageView.widthAnchor.constraint(equalTo: theImageView.heightAnchor, multiplier: 4.0/3.0).isActive = true
// add it to the stack view
theStackView.addArrangedSubview(theImageView)
// add another label
theLabel = UILabel()
// multi-line
theLabel.numberOfLines = 0
// yellow background to make it easy to see
theLabel.backgroundColor = .green
// add 2 lines of text to the label
theLabel.text = (1...2).map({ "Line \($0)" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// add another UIImageView
theImageView = UIImageView()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theImageView.translatesAutoresizingMaskIntoConstraints = false
// load a different image for it - I have one named AquariumBG
if let img = UIImage(named: "AquariumBG") {
theImageView.image = img
}
// let's give this image view a 1:1 width:height ratio
theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor, multiplier: 1.0).isActive = true
// add it to the stack view
theStackView.addArrangedSubview(theImageView)
}
}
If the steps have been followed, you should get this output:
and, after scrolling to the bottom:
Alignment constraints (leading/trailing/top/bottom)
The alignment constraint between Scroll View and Content View defines the scrollable range of the content. For example,
If scrollView.bottom = contentView.bottom, it means Scroll View is
scrollable to the bottom of Content View.
If scrollView.bottom = contentView.bottom + 100, the scrollable
bottom end of Scroll View will exceed the end of Content View by 100
points.
If scrollView.bottom = contentView.bottom — 100, the bottom of
Content View will not be reached even the scrollView is scrolled to
the bottom end.
That is, the (bottom) anchor on Scroll View indicates the (bottom) edge of the outer frame, i.e., the visible part of Content View; the (bottom) anchor on Content View refers to the edge of the actual content, which will be hidden if not scrolled to.
Unlike normal use cases, alignment constraints between Scroll View and Content View have nothing to do with the actual size of Content View. They affect only “scrollable range of content view” but NOT “actual content size”. The actual size of Content View must be additionally defined.
Size constraints (width/height)
To actually size Content View, we may set the size of Content View to a specific length, like width/height of 500. If the width/height exceeds the width/height of Scroll View, there will be a scrollbar for users to scroll.
However, a more common case will be, we want Content View to have the same width (or height) as Scroll View. In this case, we will have
contentView.width = scrollView.width
The width of Content View refers to the actual full width of content. On the other hand, the width of Scroll View refers to the outer container frame width of Scroll View. Of course, it doesn’t have to be the same width, but can be other forms like a * scrollView.width + b.
And if we have Content View higher (or wider) than Scroll View, a scrollbar appears.
Content View can not only be a single view, but also multiple views, as long as they are appropriately constrained using alignment and size constraints to Scroll View.
For details, you may follow this article: Link.
I am trying to display a UILabel that may take up multiple lines but I'm having problem with how the height is resized.
Here is what it looks when I have text over a single line, displaying correctly:
When the text spans multiple lines however this happens:
Here's the interface builder settings I'm using:
Ideally I'd like the text view to remain at athe top of the screen and just take up as much space as it needs to diaplay the text but I really can't tell where I am going wrong.
The text view is a bit tricky to handle with automatic layout. If possible use an UILabel. If not then there are several issues with the text view and the most manageable solution is to add the height constraint which is then manipulated in the code.
The height of the text view content can be determined as:
let height = textView.sizeThatFits(textView.frame.size).height
It is also possible to use
let height = textView.contentSize.height
But the results are sometimes incorrect.
You do need to then set the delegate for the text view so that on change you will refresh the size of the text view.
Well you did give it permission to do so based on your constraints. Any height > 0 as long as it's 20 from the top margin. Since you don't have any other views to base your height off of you can hook up an outlet to your label and use this:
#IBOutlet weak var label: UILabel!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
label.sizeToFit()
}
Uncheck the "Preferred Width" explicit checkbox(In Size Inspector)
Remove the height constraint on you UILabel.
It will definitely work.
I have tableView cell with different content(views, labels, imageViews) in one cell. But in something cells content can be not full. How can i use resizing cells without removing and adding always constraints? Thanks.
One of possible solutions for this problem:
Add constraints for hidden state with priority 1000
Add extra constraints for resized state with lower priority (ex 750)
Save constraints that is ONLY for hidden state into IBOutlet collection
Save constraints that is ONLY for resized state into another IBOutlet collection
Code:
#IBOutlet var hiddenConstraints: [NSLayoutConstraint] = []
#IBOutlet var visibleConstraints: [NSLayoutConstraint] = []
func hide(_ hide: Bool) {
for hiddenConstraint in self.hiddenConstraints {
hiddenConstraint.isActive = hide
}
for visibleConstraint in self.visibleConstraints {
visibleConstraint.isActive = !hide
}
self.layoutIfNeeded()
}
There is faster solution:
Move content that can be hidden into container view
Set height constraint for container view
Change from code height constraint constant to 0 if hidden or to proper height if visible
Code:
#IBOutlet var heightConstraint: NSLayoutConstraint!
func hide(_ hide: Bool) {
self. heightConstraint.constant = hide ? 0 : 150 //Estimated height
self.layoutIfNeeded()
}
This is not a good approach, as it will lead to constraint crashes at runtime. So I prefer to use first one.
Also you will need to update your cell from table to move other cells up or down.
Ray Wenderlich has a fantastic tutorial on dynamic sizing of table cells that can be found here:
https://www.raywenderlich.com/129059/self-sizing-table-view-cells
TL;DR You need to make sure your cell's content is pinned on all four sides to the cell's content view, as well as setting as high priority vertical hugging, greater than or equal to height constraint on your label.
I'm developing an app that displays HTML content stored locally.
My storyboard scene consists of a customised menu view ('MyMenuBar'), and a 'containerView', where I would like the HTML to be loaded. Both of these items are properly constrained in the storyboard.
I'm attempting to use WKWebView, as recommend by Apple. I want to add the WKWebView as a subview of the 'containerView', and give it identical constraints, however I cannot get this right. Content drops off the edge of the screen, or doesn't display at all.
Here is what I have:
#IBOutlet weak var MyMenuBar: UIView!
#IBOutlet weak var containerView: UIView!
...
var myWebView: WKWebView?
//View did load...
webViewWK = WKWebView()
containerView.addSubview(myWebView!)
myWebView?.frame = containerView.frame
myWebView?.load(localFileURL!)
I'm not sure what is going wrong here. I have tried:
view.insertSubview(myWebView!, belowSubview: MyMenuBar)
and
containerView = myWebView!
But these display nothing. I have also tried:
myWebView?.translatesAutoresizingMaskIntoConstraints = false
However, I still experience the same unusual layout issue with content incorrectly scaled, and falling off the screen.
I can solve this problem by simply using UIWebView on the storyboard, but I want the performance and future-proofing of WKWebView, and can't understand why I can't add it programmatically with correct constraints.
I am using Xcode 8.0 and Swift 3
Many thanks
What worked for me is to specify the autoresizing mask in addition to the coords for the sublayout, hinted from Sparky's answer
myWebView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
myWebView.frame = CGRect(origin: CGPoint.zero, size: containerView.frame.size)
I think the issue is with the line:
MyWebView?.frame = containerView.frame
Remember that when you add a subview to a container view, the frame for the subview must be specified in coordinates relative to the parent view. If your container view had coordinates for its origin of (100, 100), for example, this means that the web view will have its origin at an offset from the container view of (100, 100), not at the origin of the container view.
The correct way to specify the frame would be:
myWebView?.frame = CGRect(origin: CGPoint.zero, size: containerView.frame.size)
I think this should fix your issue. Hope that helps!
Try to put you code into viewDidAppear instead of viewDidLoad. It works for me with Sparky's answer :
videoWebKitView = WKWebView(frame: CGRect(origin: CGPoint.zero, size: videoContainer.frame.size))
videoContainer.addSubview(videoWebKitView)
It doesn't work for me until I move my code.
I am trying to build an interface with the IB under Xcode with some Containers View.
I will try to do my best to explain the problem:
I have a main scene which contains two controllers. The first one, at the top of the scene, contains a "Last Post View", which retrieves the last post from a Wordpress website and displays the cover image, the post's date and the title.
The second one contains a Collection View which leads to other views.
Functionally and independently, everything seems to work fine. The problem is that I can not figure how to make work this "stack" with autolayout and fit on portrait and landscape modes and different devices.
Here is my Storyboard
The Home Controller's constraints
The Last Post View's constraints
The Collection View's constraints
..and finally, what I get
After hours of searching and attempts, I found that the Scroll View, contained in my Home Controller, must have only one direct child. But I don't know how to put the different constraints. Plus, I always get the message: Scrollable content size is ambiguous for "Scroll View".
Another problem that I have, is when I am in landscape mode, I can't scroll the "whole view". At best, I can scroll the Collection View only (when I can display it) but not the entire screen.
(Maybe it can help if I said that I am using Swift 2)
Does anyone have a suggestion? It will be much appreciated!
Many thanks!
EDIT 1
I tried to apply the Xingou's solution and I think I am quite close the goal but I obviously miss something.
Here is my HomeViewController
class HomeViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var containerViewHeightConstrait: NSLayoutConstraint!
#IBOutlet weak var lastPostContainerView: UIView!
#IBOutlet weak var scrollContainerView: UIView!
#IBOutlet weak var mainCollectionContainerView: UIView!
/*************************/
/***** VIEW DID LOAD *****/
/*************************/
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
self.lastPostContainerView.setNeedsDisplay()
self.mainCollectionContainerView.setNeedsDisplay()
self.containerViewHeightConstrait.constant = UIScreen.mainScreen().bounds.height - 64
//END viewDidLoad
}
...
/********************************/
/***** SET CONTAINER HEIGHT *****/
/********************************/
func needSetContainerHeight( height: CGFloat ) {
self.containerViewHeightConstrait.constant = height + lastPostContainerView.bounds.height + 200
self.scrollView.contentSize.height = height + lastPostContainerView.bounds.height + 200
print( "View Height Constrait \( self.containerViewHeightConstrait.constant )" )
print( "Scroll View Height \( self.scrollView.contentSize.height )" )
//END needSetContainerHeight
}
...
...and my MainCollectionController
class MainCollectionViewController: UICollectionViewController {
/*************************/
/***** VIEW DID LOAD *****/
/*************************/
override func viewDidLoad() {
super.viewDidLoad()
collectionView!.autoresizingMask = [ .FlexibleWidth ]
//END viewDidLoad
}
/****************************************/
/****************************************/
/***************************/
/***** VIEW DID APPEAR *****/
/***************************/
override func viewDidAppear( animated: Bool ) {
super.viewDidAppear( animated )
( self.parentViewController as! HomeViewController ).needSetContainerHeight( self.collectionView!.contentSize.height )
//END viewDidAppear
}
...
If I well understood what was proposed, here is how constraints should look like :
(Main) View
Scroll View
Subviews Container
Last Post Container
Collection Container
List of all constraints
... and what I get
Portrait
Landscape
I made a few tests with different extra constraints and I found out that I had to tell the Scroll View to fill its whole parent to display something on the screen (otherwise, I just get the red background).
Another thing is if I add a constraint for the space between Last Post Container and Collection Container, things are "well" positioned but I cannot click on the collection's cells anymore. But, if I don't add this constraint, things are more messy (for example, the collection view overlaps the post view), but I am able to click on the cells.
Unfortunately, the screen seems to be cropped and there are some differences when I rotate the screen. But I think I have to recompute the heights when the device is rotated (am I right?). The margin at the top of screen still here, but not always: it depends in which mode I started the app and how many rotation I do.
Another thing I forgot to mention, is the last post is asynchronously retrieved. So, the cover image, which has a variable size, is displayed after the main screen is displayed. I moved this in the AppDelegate - didFinishLaunchingWithOptions and stocked the post in the Notification Center but unfortunately, there's still some delay.
Any suggestions? :)
SOLUTION
Like Xingou said, it is far easier to use the header section of the collection view (Accessories / Section Header).
you need to solve the following thing:
in you home sense, viewdidload , add the following code:
self.automaticallyAdjustsScrollViewInsets = false
this can make the red area between navagationbar and you picture disappear.
fixed the Scrollable content size is ambiguous for "Scroll View":
this is because the scrollview did not know the content size it would show。you need reset the constrain of the view( the two container view's super view ),i will call it scrollcontainerview:
scrollcontainerview.leading = scrollview.leading
scrollcontainerview.trailing = scrollview.trailing
scrollcontainerview.top = scrollview.top
scrollcontainerview.bottom = scrollview.bottom
scrollcontainerview.width = self.view.width
scrollcontainerview.height = 603 //we will chang this value through code
now the "the Scrollable content size is ambiguous for "Scroll View"" error should be disappeared.
change the scrollcontainerview.height in code :
in you home scene, drag the scrollcontainerview.height constraint into you view controller,and chang it to fit the screen:
#IBOutlet weak var contianerViewHeightConstrait:
NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
//because we set the height at const 603,but height is different on different device,so we need chang it to the correct value
contianerViewHeightConstrait.constant = UIScreen.mainScreen().bounds.height - 64
}
and now you can see the two container view fill the screen。
need scroll the whole content, not only the collection view:
to solve this problem ,you need assign a correct height to the scrollview.contentsize.height .
in you collection view controller:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
(self.parentViewController as! HomeScenceViewController).needSetContainerHeight(self.collectionView.contentSize.height)
}
then add a method in HomeScenceViewController
// set the containerview and scrollview height
func needSetContainerHeight(height:CGFloat){
//the 200 is your first container view's height
contianerViewHeightConstrait.constant = height + 200
scrollview.contentSize.height = height + 200
}
and now you should can scroll the whole content