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.
Related
I'm trying to set the width and height of my child view controller's frame, but it's not working. Here's what I'm trying to do:
let frame = UIScreen.main.bounds
let vc = FieldsTableViewController()
addChild(vc)
view.addSubview(vc.view)
vc.didMove(toParent: self)
vc.view.frame = CGRect(x: (frame.maxX+frame.minX)/2, y: 300, width: 200, height: 5)
The x and y positions are correctly set, but no matter what I try I can't seem to set the width and the height. The weird thing is that I am using the same child vc in another vc, with the exact same code as above, and I have no problem setting the width and height there.
I also tried using Auto-Layout but was not able to make it work for a child view controller (is that possible? I didn't find any example).
Thanks for your help.
I found a way around it.
I finally realized that the issue occurs when I set the view of my view controller, like so :
let v = PlayerSelectionView()
view = v
I'm not sure why that caused the problem described, but adding my view as a subview instead fixed it:
let v = PlayerSelectionView()
v.frame = view.frame
view.addSubview(v)
Now I'm able to edit the width and height of my child view controllers.
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.
I've created a subclass of UIControl called 'TestButton' with a label and an imageview subview. That object is created with a frame, and as part of the init process I create the subview elements.
These 'TestButtons' are created programmatically, I never use them in the StoryBoard.
Code snippet:
class TestButton: UIControl {
var iconImageView: UIImageView?
var labelView: UILabel?
required init(size: CGSize, icon: UIImage? text: String?) {
super.init(frame: CGRect(x: 0, y: 0, width: size.width, height: size.height) )
if ( (icon != nil) && (text != nil) ) {
self.iconImageView = UIImageView()
self.iconImageView?.translatesAutoresizingMaskIntoConstraints = false
self.iconImageView?.contentMode = .center
self.iconImageView?.image = icon
self.iconImageView?.backgroundColor = UIColor.yellow // test: show bounds
self.labelView = UILabel()
self.labelView?.translatesAutoresizingMaskIntoConstraints = false
self.labelView?.text = "Test"
self.labelView?.backgroundColor = UIColor.blue
self.addSubview(self.iconImageView!)
//self.addSubview(self.labelView!)
// Setup constraints on created subview(s)
self.iconImageView?.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
self.iconImageView?.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
self.iconImageView?.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
self.iconImageView?.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
}
override func layoutSubviews() {
super.layoutSubviews()
print("iconframe: \(self.iconImageView!.frame)")
}
In the sample above, I've removed the label from the mix. I'm only trying to get the imageView constraints to work and effectively size the imageView to the view. This does not work, the image appears full size and the constraints appear to have been ignored. I've tried moving the constraints code into updateConstraints and calling that - all appears to work but again the constraints are not applied.
layoutSubviews does get called when you would expect it to be but the imageView frame is unmodified. There are no messages in the output window, it just silently doesn't work.
My question is; have I somehow disabled autoLayout by specifying the parent's frame? I would have expected autoLayout to still work within the bounds of the parent's frame?
Sorry if this has been answered once or many times before. I'm not actually sure what I'm searching for or the correct question to ask, only posted after a day of trawling SO. Thanks
The behaviour of TestButton view will depend on how it is constrained within its superview.
In NSLayoutConstraints both participating attributes (or anchors) are equal "partners": with just those four constraints you have, imageView will take full frame of it's parent (TestButton), but at the same time TestButton will be expanded to be big enough for a full-size image.
You can apply other constraints to TestButton view to prevent the latter.
To understand why standard views behave like that, look at intrinsicContentSize property (docs). It is implemented by standard controls, and tells the auto layout system how big the view should be, purely based on it's content (UIButton or UISwitch are auto-sized like that, for example). UIImageView's intrinsicContentSize is the size of its image, that's why it expands full-size if nothing is preventing it.
I am currently making a flashcard app, and I am trying to make a button that creates an entirely new View (or subView) for the user to edit. Should I use Container Views? Collection Views? I also want to have these set to only one View Controller, so I can save the "cards" in one "set". Please Help!!
Edit: How can I save these views in a "folder" so the user can look at them later. Is there an efficient way to do this so the app doesn't slow or stop.
Edit #2: Ok, so I'm kind of getting it... collection views. But how would I implement this into my because I am using tvOS. Any thoughts?
If you want to create a new UIView programmatically, it's actually quite simple. You could create a method like this:
func addNewView(to container: UIView) {
let newView = UIView()
container.addSubview(newView)
newView.backgroundColor = UIColor.blue
newView.frame = CGRect(x: 10, y: 50, width: 200, height: 250)
}
That would create a new view inside whichever container view you passed in with a blue background, 10pts from the left (x-axis), 50pts from the top (y-axis, think a normal cartesian coordinate system with the y-axis inverted), width of 200 and height of 250.
You could then call this method on the push of a button by handling a button tap with it's own method like this:
func buttonTapped(_ sender: UIButton) {
addNewView(to: self.view)
}
Obviously all the values for the frame I gave you were just for an example so you could visualize it in your head, you can edit those however you want, or make calculations based on the size of your device's screen. You can get the device's screen size by saying self.view.bounds
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