Flipping the positions of items in a UIStackView - swift

I have a UIStackView, created in Interface Builder that contains two views. Now I'm trying to programatically change the order of these views (exchange their positions, so to speak).
Is there an easy fix for this that DOES NOT use Interface Builder to accomplish this?
Thanks :).

Create a layout with 2 UIViews in a UIStackView like so:
Create an outlet for your UIStackView to your UIViewController. Call addArrangedSubview on your UIStackView in viewDidLoad to change the order. See snippet below.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// Switches order of the stackView
stackView.addArrangedSubview(self.stackView.subviews[0])
}
}
This should be the result:
The addArrangedSubview takes the given view and adds it to the end of the arrangedSubviews array. In our case we take the red view at index[0] and add it on the end (right) of the UIStackView.

You can change the view semantic of UIStackView from Unspecified to Force Right-to-Left to swap the position of of nested view.

you can use addArragnedSubview to reorder the views.
Only addArragnedSubview is needed because when adding the view to a new parent it is removed from the old parent.
if self.viewsSwitched{
stackview.addArrangedSubview(self.stackview.subviews[0])
self.viewsSwitched = false
}else{
stackview.addArrangedSubview(self.stackview.subviews[1])
self.viewsSwitched = true
}
This code exchanges the two views inside the stackview by each call

Related

get height of a tableView

I have a tableView between a label which is on top (constraints: align center x, align top to 20).
A button is on the bottom (constraints: align center x, align bottom to 50).
And the tableView is aligned between those two objects by top/bottom to 20.
I am trying to read out the height of the tableView, because depending which device I use the height should differ?
But if I use in my code the following, I always get the same height for the tableView.
The print result is always 616.
What am I missing/not understanding correctly?
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
print(tableView.frame.size.height)
}
Try this method
override func viewWillLayoutSubviews() {
super.viewDidLayoutSubviews()
print(tableView.frame.size.height)
}
The reason that you see two different values printed is because this method is called right after viewdidload and once again after all the auto layout or auto resizing calculations on the views have been applied. Meaning the method viewDidLayoutSubviews is called every time the view size changes and the view layout has been recalculated.
For more info on this refer to
Apple documentation on viewdidlayoutsubviews
try getting your height in
override func viewWillLayoutSubviews() {
print(tableView.frame.size.height)
}
When your view controller is created from a storyboard, all initial frames are equal to your storyboard selected ones, so they will be correct only to device selected in the storyboard
First time you can access real frames is after first layoutSubviews, so you need this:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(tableView.frame.size.height)
}
Check out more about view lifecycle here
p.s. newer forget to call super. method when you're overriding something, you may break something easily. Unless the documentation says so or you're really knowing what you're doing

having a custom UIView in a UIViewcontroller

I created a UIView class and implemented it inside a viewcontroller. However, I have a some other things in the view controller that I want to be able to interact with but I cannot do that because of the attached view.
how do I interact with the base viewcontroller while the custom uiview is present in the viewcontroller
var tripView: TripView!
override func viewDidLoad() {
super.viewDidLoad()
tripView = TripView(frame: CGRect.zero)
self.view.addSubview(tripView)
// AutoLayout
tripView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets.zero)
}
You should assign proper index to your views by using following methods from UIView.
Instance Methods: aboveSubview and belowSubview or insertSubview
.
From UIView overview
Views can be nested inside other views to create view hierarchies, which offer a convenient way to organize related content. Nesting a view creates a parent-child relationship between the child view being nested (known as the subview) and the parent (known as the superview). A parent view may contain any number of subviews but each subview has only one superview. By default, when a subview’s visible area extends outside of the bounds of its superview, no clipping of the subview's content occurs.
Hope it helps!
You can do
class TripView:UIView {
// add this
class TransView:UIView {
var imgV1:UIImageView!
var imgV2:UIImageView!
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return imgV1.frame.contains(point) || imgV2.frame.contains(point) // or ![imgV1,imgV2].filter{$0.frame.contains(point)}.isEmpty
}
}

Is it possible for UIStackView to get focus in tvOS?

I have two UILabels in a UIStackView. Is it possible for the UIStackView to get focus and gestures in tvOS?
Items in a stack view can receive focus, but not the stack view itself. By default, labels cannot receive focus either. Buttons are the most commonly items that receive focus.
You can make a stack view focusable by subclassing the stackview, overriding canBecomeFocused and using that subclass:
class FocusableStackView: UIStackView {
override var canBecomeFocused: Bool {
return true
}
}

How to handle iOS 11 large title animation when using multiple container views?

I am making an app at the moment where 1 screen has a segmented control with 3 segments. Initially I had 1 table view and when you change segment I would simply change the data source/cell etc and reload the table. While this works great there is always the problem that when you change segments it will not remember your last scroll position because the table view gets reloaded.
I tried to get around this with storing offset position, rows etc but I could never get it to work like I wanted. Seems especially annoying when you have different cell types for the segments and they are self sizing as well.
I than decided to have a master view controller with the segmented control and 3 container views with their own VC and table view for each segment. I simply hide/show the correct container view when changing segments. This also works great but I have 1 problem with iOS 11 style large headers. Only the 1st container view added as a subview to the ViewControllers view manipulates the collasping/expanding of the title when you scroll.
Therefore when I change to the 2nd or 3rd container view and start scrolling I do not get the large title collapsing animation. How can I get around that?
I tried the following
1) Change Container view zPosition when changing segments
2) Move the container view to the front by calling view.bringSubview(toFront: ...)
3) Looping through the subviews and calling
view.exchangeSubview(at: 0, withSubviewAt: ...)
I believe I could remove all container views and add the one I need again and give them constraints but I wonder if there is a more straight forward solution.
Or if someone has a good solution to remember a tableViews scroll position before reloading it I would appreciate that too.
So I found an answer that seems to work for me, thanks to this great article. https://cocoacasts.com/managing-view-controllers-with-container-view-controllers/
Essentially what I did is
1) Remove the ContainerViews and Segues from the MasterViewController Storyboard.
2) Add a lazy property for each VC of the segmented control in the MasterViewController. They are lazy so that they only get initialised when you actually need them
lazy var viewController1: LibraryViewController = {
let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
// do other set up if required.
return viewController
}()
lazy var viewController2: LibraryViewController = {
let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
// do other set up if required.
return viewController
}()
3) Create an extension of UIViewController with the following 2 methods. I added them in an extension purely for code organisation as they might be reused on other ViewControllers.
extension UIViewController {
func add(asChildViewController viewController: UIViewController) {
// Add Child View Controller
addChildViewController(viewController)
// Add Child View as Subview
view.addSubview(viewController.view)
// Configure Child View
viewController.view.frame = view.bounds
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
func remove(asChildViewController viewController: UIViewController) {
// Notify Child View Controller
viewController.willMove(toParentViewController: nil)
// Remove Child View From Superview
viewController.view.removeFromSuperview()
// Notify Child View Controller
viewController.removeFromParentViewController()
}
}
4) Now in my segmented control method that gets called when you change segment I simply add the correct ViewController. The nice thing is that remove/adding them does not actually deallocate them.
func didPressSegmentedControl() {
if segmentedControl.selectedSegmentIndex == 0 {
remove(asChildViewController: viewController2)
add(asChildViewController: viewController1)
} else {
remove(asChildViewController: viewController1)
add(asChildViewController: viewController2)
}
}
5) Make sure you call the method at point 4 in ViewDidLoad so that the correct VC is added when the VC is loaded the 1st time.
func viewDidLoad() {
super.viewDidLoad()
didPressSegmentedControl()
}
This way when we remove a ChildViewController and add another one it will always be the the top VC in the subviews array and I get my nice title collapsing animation.
Another added benefit of this approach is that if you never go to a particular segment that particular VC will never get initialised, because they are lazy properties, which should help with efficiency.
Hope this helps somebody trying to do the same.
This is a horrible issue which I hope will be resolved soon, but there is another fix - although I freely admit that this is a nasty hack.
Basically, the issue only applies to the FIRST container view in the hierarchy. Add another container view to your hierarchy. Set it as hidden, then delete its segue and its target view controller just to be neat. Finally, make sure that this is the first container view in the hierarchy by dragging it to the top of the list.

Making UIScrollView with an UIImageView scale content automatically using Swift, autolayout and SnapKit

I have a view controller set up in Interface Builder with the following view structure:
- UIView
* UIScrollView
* UIImageView
I want to assign an image to UIImageView so, that the scroll view (and it's content size) will adopt the same width than in the image and automatically calculate the height of the content size according to the image aspect ratio. (This makes the image vertically scrollable inside the UIScrollView.)
I'm using Swift and Interface Builder + SnapKit for managing autolayout constraints.
I managed to make the constraints using SnapKit in the following way:
In Interface Builder, select the view controller and create all missing constraints by selecting Editor / Resolve Auto Layout Issues / Add Missing Constraints
Manually select all generated constraints and select Placeholder / Remove at build time from Attributes Inspector
Assign UIScrollView and UIImageView outlets to the view controller
Assign an image to the UIImageView
Then implement the view controller as follows:
import UIKit
import SnapKit
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.snp.makeConstraints { (make) in
make.edges.equalTo(view)
}
let ratio = imageView.image!.size.height / imageView.image!.size.width
imageView.snp.makeConstraints { (make) in
make.top.equalTo(0.0)
make.bottom.equalTo(scrollView.snp.bottom)
make.width.equalTo(view.snp.width)
make.height.equalTo(scrollView.snp.width).multipliedBy(ratio)
}
}
}