I have simple ViewController which displays images using UIScrollView which has constraints attaching it to the (top, leading, trailing) of the superView, and a UIPageControl, it works fine on iPhoneX simulator
When I run it on iPad Pro 9.7" simulator, the output is
After changing the View as attribute in the storyboard from iPhoneX to iPadPro 9.7" it worked well
This is the logic I use to calculate scrollviewContentSize & slidesSize
override internal func viewDidLoad() {
super.viewDidLoad()
tutorialScrollView.delegate = self
viewModel = TutorialViewModel()
configurePages()
setupSlideScrollView(slides: slides)
configurePageControl()
}
private func configurePages() {
if let viewModel = viewModel {
createSlides(tutotialPages: viewModel.getTutorialPages())
}
}
private func createSlides(tutotialPages: [TutorialPage]) {
for page in tutotialPages {
if let slide = Bundle.main.loadNibNamed(BUNDLE_ID, owner: self, options: nil)?.first as? TutorialSlideView {
slide.configure(title: page.title, detail: page.details, image: page.image)
slides.append(slide)
}
}
}
private func setupSlideScrollView(slides: [TutorialSlideView]) {
tutorialScrollView.contentSize = CGSize(width: view.frame.width * (CGFloat(slides.count)), height: tutorialScrollView.frame.height)
tutorialScrollView.isPagingEnabled = true
for i in 0 ..< slides.count {
slides[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: tutorialScrollView.frame.height)
tutorialScrollView.addSubview(slides[i])
}
}
Can anyone find the problem?
Did you try to print view's frame in setupSlideScrollView method to ensure it is correct? There is no guarantee that it will be correct in the viewDidLoad method if you use AutoLayout. Sometimes it will be, sometimes not. I assume in this particular case, it happened to be correct on iPhone X, but incorrect on iPad.
If that's the problem, you should set contentSize and slides' frames in viewDidLayoutSubviews. Adding slides as subviews should stay in viewDidLoad/setupSlideScrollView because viewDidLayoutSubviews usually gets called multiple times.
Related
I have a weird problem. I'm trying to calculate the exact height between the bottom of the navigation bar and the top of the keyboard no matter which iOS device I'm running the app on. Here is the method where I'm doing this calculation:
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
let navigationBarHeight: CGFloat = self.navigationController!.navigationBar.frame.height
let viewableArea = screenSize.height - keyboardRectangle.height - reportPostInstructionContainerViewHeight - characterCountContainerViewHeight - reportPostInstructionContainerViewHeight + 4
//iPhone 12 and above is "- 20"; iPhone 8 needs to be "+ 4"; iPhone 12 mini is "- 24"
print("**** navigationBarHeight: \(navigationBarHeight)")
print("**** keyboardHeight: \(keyboardHeight)")
print("**** screenSize.height: \(screenSize.height)")
print("**** total screen height - keyboard height: \(screenSize.height - keyboardRectangle.height)")
print("**** viewableArea: \(viewableArea)")
textViewHeight = viewableArea
print("**** textViewHeight: \(textViewHeight)")
UIView.animate(withDuration: 0.01, animations: { () -> Void in
self.textView.anchor(
top: self.horizontalDividerView.bottomAnchor,
leading: self.view.leadingAnchor,
bottom: nil,
trailing: self.view.trailingAnchor,
identifier: "ReportPostPFAVC.textView.directionalAnchors",
size: .init(width: 0, height: self.textViewHeight)
)
self.textView.layoutIfNeeded()
})
}
}
The line where "viewableArea" is being calculated seems to be the issue. For example, if I'm running the app on an iPhone 8, I need to add 4 to this calculation in order to size the text view properly.
Here is an image for reference:
I'm trying to get the bar with the "Report" button to sit perfectly on top of the keyboard. But, if I test on different devices sometimes I need to subtract 20 or 24 instead of adding 4.
I don't really understand where this gap is coming from.
Can anyone advise?
I will try to approach this from a different angle as I am not sure where exactly your issue is and what exactly was your logic from the code alone that you provided.
What you need to achieve is to find the coordinates of two frames in the same coordinate system. The two frames being; the keyboard frame and the navigation bar frame. And the "same coordinate system" is best defined by one of your views such as the view of your view controller.
There are convert methods on UIView which are designed to convert frames to/from different coordinate systems such as views.
So in your case all you need to do is
let targetView = self.view!
let convertedNavigationBarFrame = targetView.convert(self.navigationController!.navigationBar.bounds, from: self.navigationController!.navigationBar)
let convertedKeyboardFrame = targetView.convert(keyboardFrame.cgRectValue, from: nil)
In this example I used self.view as my coordinate system in which I want the two frames. This means the coordinates will be within a view controller. To get a height between two frames (which is your question) I could use absolutely any view that is in same window hierarchy and I should be getting the same result.
Then in this example I convert bounds of navigation bar from navigation bar, to target view. I found this to be best approach when dealing with UIView frames.
Last I convert keyboard frame to target view. The keyboard frame has a screen coordinate system which leads to using from: nil.
Getting the vertical distance between them is then a simple subtraction of two vertical coordinates
convertedKeyboardFrame.minY - convertedNavigationBarFrame.maxY
To have a full example I cerated a new project. In storyboard:
I added a navigation controller
I set the navigation controller to be "initial".
I set the old auto-generated view controller to be the root view controller of the navigation controller.
I added a text field which will trigger the
appearance of keyboard.
Then applied the following code:
The example code:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
self.navigationController?.navigationBar.backgroundColor = .green
}
private lazy var checkView: UIView = {
let checkView = UIView(frame: .zero)
checkView.backgroundColor = UIColor.black
checkView.layer.borderColor = UIColor.red.cgColor
checkView.layer.borderWidth = 5
self.view.addSubview(checkView)
return checkView
}()
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let targetView = self.view!
let convertedNavigationBarFrame = targetView.convert(self.navigationController!.navigationBar.bounds, from: self.navigationController!.navigationBar)
let convertedKeyboardFrame = targetView.convert(keyboardFrame.cgRectValue, from: nil)
checkView.frame = CGRect(x: 30.0, y: convertedNavigationBarFrame.maxY, width: 100.0, height: convertedKeyboardFrame.minY - convertedNavigationBarFrame.maxY)
}
}
}
The checkView appears between navigation bar and keyboard to show that the computation is correct. The view should fill the space between the two items and border is used to show that this view does not stretch below keyboard or above navigation bar.
I am practicing auto-layout programmatically. I want to put a UIView centered in the controller whose width will be 4/5 in portrait mode but when it will go to the landscape mode, I need the height to be of 4/5 of the super view's height, rather than the width.
Something like -
So, I am deactivating and then activating the constrains required depending on the orientation but when I change rotation, it gives me conflict as if it didn't deactivated the ones, I specified to be deactivated. Here is my full code. As It is storyboard independent, one can just assign the view controller class to a view controlller and see the effect.
class MyViewController: UIViewController {
var widthSizeClass = UIUserInterfaceSizeClass.unspecified
var centeredView : UIView = {
let view = UIView()
view.backgroundColor = UIColor.systemGreen
return view
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.view.addSubview(centeredView)
centeredView.translatesAutoresizingMaskIntoConstraints = false
}
override func viewWillLayoutSubviews(){
super.viewWillLayoutSubviews()
widthSizeClass = self.traitCollection.horizontalSizeClass
addConstrainsToCenterView()
}
func addConstrainsToCenterView() {
centeredView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor).isActive = true
centeredView.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor).isActive = true
let compactWidthAnchor = centeredView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 4/5)
let compactHeightAnchor = centeredView.heightAnchor.constraint(equalTo: centeredView.widthAnchor)
let regularHeightAnchor = centeredView.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor, multiplier: 4/5)
let regularWidthAnchor = centeredView.widthAnchor.constraint(equalTo: centeredView.heightAnchor)
if widthSizeClass == .compact{
NSLayoutConstraint.deactivate([regularWidthAnchor, regularHeightAnchor])
NSLayoutConstraint.activate([compactWidthAnchor, compactHeightAnchor])
}
else{
NSLayoutConstraint.deactivate([compactWidthAnchor, compactHeightAnchor])
NSLayoutConstraint.activate([regularWidthAnchor, regularHeightAnchor])
}
}
}
Can anyone please help me detect my flaw.
Couple issues...
1 - many iPhone models only have wC hR (portrait) and wC hC (landscape) size classes. So, if you're checking for the .horizontalSizeClass on those devices it will always be .compact. You likely want to be checking the .verticalSizeClass
2 - the way you have your code, you are creating NEW constraints every time you call addConstrainsToCenterView(). You're not activating / deactivating existing constraints.
Take a look at this:
class MyViewController: UIViewController {
var heightSizeClass = UIUserInterfaceSizeClass.unspecified
var centeredView : UIView = {
let view = UIView()
view.backgroundColor = UIColor.systemGreen
return view
}()
// constraints to activate/deactivate
var compactAnchor: NSLayoutConstraint!
var regularAnchor: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(centeredView)
centeredView.translatesAutoresizingMaskIntoConstraints = false
// centeredView is Always centerX and centerY
centeredView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor).isActive = true
centeredView.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor).isActive = true
// for a square (1:1 ratio) view, it doesn't matter whether we set
// height == width
// or
// width == height
// so we can set this Active all the time
centeredView.heightAnchor.constraint(equalTo: centeredView.widthAnchor).isActive = true
// create constraints to activate / deactivate
// for regular height, set the width to 4/5ths the width of the view
regularAnchor = centeredView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 4/5)
// for compact height, set the height to 4/5ths the height of the view
compactAnchor = centeredView.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor, multiplier: 4/5)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// use .verticalSizeClass
heightSizeClass = self.traitCollection.verticalSizeClass
updateCenterViewConstraints()
}
func updateCenterViewConstraints() {
if heightSizeClass == .compact {
// if height is compact
regularAnchor.isActive = false
compactAnchor.isActive = true
}
else{
// height is regular
compactAnchor.isActive = false
regularAnchor.isActive = true
}
}
}
With that approach, we create two vars for the constraints we want to activate/deactivate:
// constraints to activate/deactivate
var compactAnchor: NSLayoutConstraint!
var regularAnchor: NSLayoutConstraint!
Then, in viewDidLoad(), we add centeredView to the view, set its "non-changing" constraints - centerX, centerY, aspect-ratio - and create the two activate/deactivate constraints.
When we change the size class, we only have to deal with the two var constraints.
Possibly not an answer, but to go along with my comment, here's code I use successfully:
var p = [NSLayoutConstraint]()
var l = [NSLayoutConstraint]()
Note, p and l are arrays and stand for portrait and landscape respectively.
override func viewDidLoad() {
super.viewDidLoad()
setupConstraints()
}
Nothing much here, just showing that constraints can be set up when loading the views.
func setupConstraints() {
// for constraints that do not change, set `isActive = true`
// for constants that do change, use `p.append` and `l.append`
// for instance:
btnLibrary.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
p.append(btnLibrary.topAnchor.constraint(equalTo: safeAreaView.topAnchor, constant: 10))
l.append(btnLibrary.bottomAnchor.constraint(equalTo: btnCamera.topAnchor, constant: -10))
Again, nothing much here - it looks like you are doing this. Here's the difference I'm seeing in your view controller overrides:
var initialOrientation = true
var isInPortrait = false
override func viewWillLayoutSubviews() {
super.viewDidLayoutSubviews()
if initialOrientation {
initialOrientation = false
if view.frame.width > view.frame.height {
isInPortrait = false
} else {
isInPortrait = true
}
view.setOrientation(p, l)
} else {
if view.orientationHasChanged(&isInPortrait) {
view.setOrientation(p, l)
}
}
}
public func orientationHasChanged(_ isInPortrait:inout Bool) -> Bool {
if self.frame.width > self.frame.height {
if isInPortrait {
isInPortrait = false
return true
}
} else {
if !isInPortrait {
isInPortrait = true
return true
}
}
return false
}
public func setOrientation(_ p:[NSLayoutConstraint], _ l:[NSLayoutConstraint]) {
NSLayoutConstraint.deactivate(l)
NSLayoutConstraint.deactivate(p)
if self.bounds.width > self.bounds.height {
NSLayoutConstraint.activate(l)
} else {
NSLayoutConstraint.activate(p)
}
}
Some of this may be overkill for your use. But instead of size classes, I actually check the bounds, along with detecting the initial orientation. For my use? I'm actually setting either a sidebar or a bottom bar. Works in all iPhones and iPads.
Again, I'm not seeing anything major - activating/deactivating a named(?) array of constraints instead of creating the arrays, the order of doing this, the override you are using... the one thing that jumps out (for me) is looking at size classes. (Possibly finding out what the initial size class is?)
I'm currently working through documenting how a UISplitViewController decided to show either the Secondary or Compact VC. Turns out that it behaves differently in at least five groups - iPad (always Secondary), iPad split screen (Compact in all iPads except iPad Pro 12.9 in landscape when half screen), iPhone portrait (always Compact), and finally, iPhone Landscape (compact for most, but Secondary for (iPhone 8 Plus, iPhone 11, iPhone 11 Pro Max, and iPhone 12 Pro Max.)
NOTE: It's Compact for iPhone 11 Pro, iPhone 12, and iPhone 12 Pro! I was surprised at this. (Next up for me is directly testing the size classes.)
My point? Maybe you need to go at the screen bounds to determine what layout you want instead of size classes. That is more in your control than size classes. Either way, good luck!
It’s quite simple. Each time layout happens and each time you say eg:
let regularWidthAnchor = centeredView.widthAnchor.constraint(equalTo: centeredView.heightAnchor)
NSLayoutConstraint.deactivate([regularWidthAnchor, regularHeightAnchor])
you are not deactivating the existing active constraint in the interface. You are creating a completely new constraint and then deactivating it (which is pointless as it was never active) and then throwing it away.
You need to create these constraints just once and keep references to them.
I am updating my app to adapt it for iPhone X. All views work fine by now except one. I have a view controller that presents a custom UIView that covers the whole screen. Before I was using UIScreen.main.bounds to find out the size of the view before all layout was done (I need it for putting the correct itemSize for a collectionView). I thought that now I could do something like
UIScreen.main.bounds.size.height - safeAreaInsets.bottom to get the right usable size. The problem is, safeAreaInsets returns (0,0,0,0) trying on an iPhone X (Simulator). Any ideas? In other views, I get the right numbers for safeAreaInsets.
Thank you!
I recently had a similar problem where the safe area insets are returning (0, 0, 0, 0) as soon as viewDidLoad is triggered. It seems that they are set fractionally later than the rest of the view loading.
I got round it by overriding viewSafeAreaInsetsDidChange and doing my layout in that instead:
override func viewSafeAreaInsetsDidChange() {
// ... your layout code here
}
I already figure out the solution: I was doing all the implementation in the init of the view. safeAreaInsets has the correct size in layoutSubviews()
I've run into this issue too trying to move up views to make way for the keyboard on the iPhone X. The safeAreaInsets of the main view are always 0, even though I know the subviews have been laid out at this point as the screen has been drawn. A work around I found, as and mentioned above, is to get the keyWindow and check its safe area insets instead.
Obj-C:
CGFloat bottomInset = [UIApplication sharedApplication].keyWindow.safeAreaInsets.bottom;
Swift:
let bottomInset = UIApplication.shared.keyWindow?.safeAreaInsets.bottom
You can then use this value to adjust constraints or view frames as required.
I have a view which is a subview inside another view.
I found that I can't get safeAreaInsets correctly, it always return 0, in that view on iPhoneX even if I put it in layoutSubviews.
The final solution is I use following UIScreen extension to detect safeAreaInsets which can work like a charm.
extension UIScreen {
func widthOfSafeArea() -> CGFloat {
guard let rootView = UIApplication.shared.keyWindow else { return 0 }
if #available(iOS 11.0, *) {
let leftInset = rootView.safeAreaInsets.left
let rightInset = rootView.safeAreaInsets.right
return rootView.bounds.width - leftInset - rightInset
} else {
return rootView.bounds.width
}
}
func heightOfSafeArea() -> CGFloat {
guard let rootView = UIApplication.shared.keyWindow else { return 0 }
if #available(iOS 11.0, *) {
let topInset = rootView.safeAreaInsets.top
let bottomInset = rootView.safeAreaInsets.bottom
return rootView.bounds.height - topInset - bottomInset
} else {
return rootView.bounds.height
}
}
}
I try to use "self.view.safeAreaInset" in a view controller. First, it is a NSInsetZero when I use it in the controller's life cycle method "viewDidLoad", then I search it from the net and get the right answer, the log is like:
ViewController loadView() SafeAreaInsets :UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
ViewController viewDidLoad() SafeAreaInsets :UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
ViewController viewWillAppear() SafeAreaInsets :UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
ViewController viewDidLayoutSubviews() SafeAreaInsets :UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)
ViewController viewDidAppear() SafeAreaInsets :UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)
so you can choice the right method that you need the safeAreaInset and use it!
Swift iOS 11,12,13+
var insets : UIEdgeInsets = .zero
override func viewDidLoad() {
super.viewDidLoad()
insets = UIApplication.shared.delegate?.window??.safeAreaInsets ?? .zero
//Or you can use this
insets = self.view.safeAreaInsets
}
In my case I was adding a UICollectionView inside viewDidLoad()
collectionView = UICollectionView(frame: view.safeAreaLayoutGuide.layoutFrame, collectionViewLayout: createCompositionalLayout())
Unfortunately at this stage safeAreaLayoutGuide is still zero.
I solved it by adding:
override func viewDidLayoutSubviews() {
collectionView.frame = view.safeAreaLayoutGuide.layoutFrame
}
the viewDidAppear(_:) method of the container view controller that extends the safe area of its embedded child view controller to account for the views in .
Make your modifications in this method because the safe area insets for a view are not accurate until the view is added to a view hierarchy.
override func viewDidAppear(_ animated: Bool) {
if (#available(iOS 11, *)) {
var newSafeArea = view.safeAreaInsets
// Adjust the safe area to accommodate
// the width of the side view.
if let sideViewWidth = sideView?.bounds.size.width {
newSafeArea.right += sideViewWidth
}
// Adjust the safe area to accommodate
// the height of the bottom view.
if let bottomViewHeight = bottomView?.bounds.size.height {
newSafeArea.bottom += bottomViewHeight
}
// Adjust the safe area insets of the
// embedded child view controller.
let child = self.childViewControllers[0]
child.additionalSafeAreaInsets = newSafeArea
}
}
I've come across the same problem. In my case the view I'm inserting would be sized correctly after calling view.layoutIfNeeded(). The view.safeAreaInsets was set after this, but only the top value was correct. The bottom value was still 0 (this on an iPhone X).
While trying to figure out at what point the safeAreaInsets are set correctly, I've added a breakpoint on the view's safeAreaInsetsDidChange() method. This was being called multiple times, but only when I saw CALayer.layoutSublayers() in the backtrace the value had been set correctly.
So I've replaced view.layoutIfNeeded() by the CALayer's counterpart view.layer.layoutIfNeeded(), which resulted in the safeAreaInsets to be set correctly right away, thus solving my problem.
TL;DR
Replace
view.layoutIfNeeded()
by
view.layer.layoutIfNeeded()
[UIApplication sharedApplication].keyWindow.safeAreaInsets return none zero
Just try self.view.safeAreaInsets instead of UIApplication.shared.keyWindow?.safeAreaInsets
Safe area insets seems to not fill on iOS 11.x.x devices when requested via application keyWindow.
View layout is never guaranteed until layoutSubviews or viewDidLayoutSubviews. Never rely on sizes before these lifecycle methods. You will get inconsistent results if you do.
To calculate safe area safeAreaInsets, try to obtain it in viewWIllAppear(), as in didLoad() the view have not been formed.
You will have the correct inset in willAppear!
In case you cannot subclass, you can use this UIView extension.
It gives you an API like this:
view.onSafeAreaInsetsDidChange = { [unowned self] in
self.updateSomeLayout()
}
The extension adds an onSafeAreaInsetsDidChange property using object association. Then swizzles the UIView.safeAreaInsetsDidChange() method to call the closure (if any).
extension UIView {
typealias Action = () -> Void
var onSafeAreaInsetsDidChange: Action? {
get {
associatedObject(for: "onSafeAreaInsetsDidChange") as? Action
}
set {
Self.swizzleSafeAreaInsetsDidChangeIfNeeded()
set(associatedObject: newValue, for: "onSafeAreaInsetsDidChange")
}
}
static var swizzled = false
static func swizzleSafeAreaInsetsDidChangeIfNeeded() {
guard swizzled == false else { return }
swizzle(
method: "safeAreaInsetsDidChange",
originalSelector: #selector(originalSafeAreaInsetsDidChange),
swizzledSelector: #selector(swizzledSafeAreaInsetsDidChange),
for: Self.self
)
swizzled = true
}
#objc func originalSafeAreaInsetsDidChange() {
// Original implementaion will be copied here.
}
#objc func swizzledSafeAreaInsetsDidChange() {
originalSafeAreaInsetsDidChange()
onSafeAreaInsetsDidChange?()
}
}
It uses some helpers (see NSObject+Extensions.swift and NSObject+Swizzle.swift), but you don't really need it if you use sizzling and object association APIs directly.
I have created two UIScrollViews ( One named scrollView and one named scrollLevel4 )
when I move scrollLevel4, I can get scrollView to move at the same speed using :-
func scrollViewDidScroll(_ scrollLevel4: UIScrollView) {
scrollView.contentOffset.x = scrollLevel4.contentOffset.x
}
However If I want to move scrollView at a different pace, not sure what to do, whenever I add anything to the end of line :
scrollView.contentOffset.x = scrollLevel4.contentOffset.x
it crashes, even a simple + 10, same pace, staggered offset, still crashes
also tried .scrollRectToVisible() method
Thoughts ?
Without seeing the error or your code it's hard to say for sure, but most likely you are setting the same delegate for both scrollViews. When you drag scrollLevel4, it triggers a scroll on scrollView, so you get an infinite loop and eventually a crash.
If you want to use the same delegate on both scrollViews, you'll need to check which one was passed before operating on them. Here's a basic working implementation. Open a new single view project and replace the code in ViewController.swift with:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
var imageView1: UIImageView!
var imageView2: UIImageView!
var scrollView1: UIScrollView!
var scrollView2: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
imageView1 = UIImageView(image: UIImage(named: "image.jpg"))
imageView2 = UIImageView(image: UIImage(named: "image.jpg"))
scrollView1 = UIScrollView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 200, height: 200)))
scrollView1.contentSize = imageView1.bounds.size
scrollView1.addSubview(imageView1)
view.addSubview(scrollView1)
scrollView2 = UIScrollView(frame: CGRect(origin: CGPoint(x: 0, y: 210), size: CGSize(width: 200, height: 200)))
scrollView2.contentSize = imageView2.bounds.size
scrollView2.addSubview(imageView2)
view.addSubview(scrollView2)
scrollView2.delegate = self
scrollView1.delegate = self
}
func scrollViewDidScroll(_ scrolled: UIScrollView) {
// both scrollViews call this when scrolled, so determine which was scrolled before adjusting
if scrolled === scrollView1 {
scrollView2.contentOffset.x = scrolled.contentOffset.x + 100
} else if scrolled === scrollView2 {
scrollView1.contentOffset.x = scrolled.contentOffset.x - 100
}
}
}
Note that whatever modification you apply to the offset of one, you have to apply the exact inverse (or nothing at all) to the other. Otherwise you'll have an infinite loop of back and forth scrolling ending in a crash.
I have created a "HorizontalSplit"-UIViewController. In that I created two UIViews as containers for other UIViewController. The height of the Containers is the half of the Screen.
override func viewDidLoad() {
super.viewDidLoad()
...
self.topViewContainer = UIView(frame: CGRectMake(0, 0, self.view.bounds.width, completeHeight / 2))
self.topViewContainer.backgroundColor = UIColor.orangeColor()
self.view.addSubview(topViewContainer)
self.bottomViewContainer = UIView(frame: CGRectMake(0, completeHeight / 2, self.view.bounds.width, completeHeight / 2))
self.bottomViewContainer.backgroundColor = UIColor.blueColor()
self.view.addSubview(bottomViewContainer)
...
}
After that I put a new Controller in that View an used these lines of code:
func setTopViewController(viewController: UIViewController) {
if self.topViewController != nil {
self.topViewController.removeFromParentViewController()
self.topViewController.view.removeFromSuperview()
}
var l = topViewContainer.frame.height
self.topViewController = viewController
self.topViewController.view.frame = CGRect(x: 0, y: 0, width: topViewContainer.bounds.width, height: topViewContainer.bounds.height)
var o = topViewController.view.frame.height
self.addChildViewController(self.topViewController)
self.topViewContainer.addSubview(self.topViewController.view)
self.topViewController.didMoveToParentViewController(self)
}
I want that the SubViewController-Frame-Size (here called simple viewController) have the same size like the UIView-container that I added before.
It works here because l and o (the variables) says the right height. But when my ViewController that I setup via the setTopViewController method on the HorizontalViewContrller the size is'n correct anymore.
When I call on viewDidLoad 'self.view.bounds.height' on the subViewController it called the full size! And not the size I have set before... Why?
Ok, I found the solution. Thanks to these post of UIViewControllers lifecycle:
In the Sub-ViewController I've get the height in viewDidLoad but in there it haven't actually the settet frame size. Also with the help of these I found that the viewWillAppear have the settet height of the frame.
Hope that helps anyone too :)