In my application, depending on whether the user is logged in, header looks differently. The problem is that by calling it in viewDidLoad - it loads incorrectly.
Here's my header code:
func configureUITableViewHeader() {
let header = HomeTableHeaderView.fromNib()
header.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 200)
header.backgroundColor = AppColors.mainThemeColor.withAlphaComponent(0.3)
let bottomLine = UIView(frame: CGRect(x:0, y: header.frame.height, width:header.frame.width , height:3))
bottomLine.backgroundColor = AppColors.detailsColor
header.addSubview(bottomLine)
switch UserAccount.shared.state {
case .verified:
header.configure(delegate: self, labeltext: "Добро пожаловать! \(String(describing: UserAccount.shared.userEmail!))")
header.logOutButtonUotlet.isHidden = false
header.logInButtonOutlet.isHidden = true
tableView.tableHeaderView = header
case .nonVerified:
header.configure(delegate: self, labeltext: "Пожалуйста, авторизуйтесь чтобы продолжить")
header.logInButtonOutlet.isHidden = false
header.logOutButtonUotlet.isHidden = true
tableView.tableHeaderView = header
default :
print("nothing to showing")
}
}
If I call it in ViewDidLoad:
If i call it in ViewDidAppear:
What could be the problem? I thought I was familiar enough with the controller lifecycle, but ...
viewDidLoad is called when the view is loaded into the memory.
Note: The view is not completely rendered at this point of time hence, you will not get the exact size of the parent view or the window.
viewDidAppear is called when the view is added to the view hierarchy. Here, the view rendering is complete. This is where you will get the correct size of it's parent view or the window.
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621423-viewdidappear
to layout the header properly I use the following code in my viewController
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var headerFrame = headerView.frame
//Comparison necessary to avoid infinite loop
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
tableView.tableHeaderView = headerView
}
}
Related
In this particular scenario, I'm actually very confused. I have subclassed UINavigationBar, noted Apple's outdated sample code, realized that sizeToFit is never called and overrode intrinsicContentSize and layoutSubviews to calculate the necessary height. Everything works well when it comes to calculating the height based on a custom view I provide (I use systemLayoutSizeFittingSize for calculating the height dynamically based on a custom content view/titleView).
The odd part is, when I attempt to animate any changes, I can animate changes in the custom titleView, but when reloading the intrinsicContentSize via invalidateIntrinsicContentSize(), the view frame changes do not animate. It snaps.
Sample code:
let delta = destinationTextFieldContainerView.frame.minX - originTextFieldContainerView.frame.minX + originTextFieldContainerView.frame.height
textContainersSeparatorConstraint.constant = -delta
UIView.animateWithDuration(2, delay: 2, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.customContainerNavigationBar.invalidateIntrinsicContentSize()
self.customContainerNavigationBar.layoutIfNeeded()
}) { (_) in
self.customContainerNavigationBar.invalidateIntrinsicContentSize()
}
The one constraint value change will animate, but unfortunately (because navBars don't rely on auto layout) it seems there's a problem updating the frame. I've attempted updating the frame directly before invalidating the intrinsic size with no success. I've also tried a height constraint, listing it in key frame animations with different timing offsets, invalidating the layout at different points in time, all to no success. Any help would be greatly appreciated.
Resizing code can be found here:
override func intrinsicContentSize() -> CGSize {
var navigationBarSize = super.intrinsicContentSize()
navigationBarSize.width = superview?.frame.width ?? 0
guard let titleView = navigationItem?.titleView else {
return navigationBarSize
}
let fittingSize = CGSize(width: navigationBarSize.width, height: UILayoutFittingCompressedSize.height)
let titleViewSize = titleView.systemLayoutSizeFittingSize(fittingSize)
navigationBarSize.height = max(titleViewSize.height, 44)
navigationBarSize.width = UIViewNoIntrinsicMetric
return navigationBarSize
}
override func layoutSubviews() {
super.layoutSubviews()
guard let navigationItem = navigationItem, titleView = navigationItem.titleView else {
return
}
var navigationBarSize = bounds.size
navigationBarSize.width = superview?.frame.width ?? 0
var fittingSize = CGSize(width: navigationBarSize.width, height: UILayoutFittingCompressedSize.height)
if let leftBarButtonItem = navigationItem.leftBarButtonItem, leftButtonCustomView = leftBarButtonItem.customView {
fittingSize.width -= leftButtonCustomView.frame.width + leftButtonCustomView.frame.origin.x + 22
}
let titleViewSize = titleView.systemLayoutSizeFittingSize(fittingSize, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow)
titleView.frame = CGRect(origin: CGPoint(x: titleView.frame.origin.x, y: 0), size: titleViewSize)
titleView.setNeedsLayout()
titleView.layoutIfNeeded()
if let leftBarButtonItem = navigationItem.leftBarButtonItem, leftButtonCustomView = leftBarButtonItem.customView {
var customViewFrame = CGRect(origin: CGPoint.zero, size: navigationBarSize)
let customViewSize = leftButtonCustomView.systemLayoutSizeFittingSize(customViewFrame.size, withHorizontalFittingPriority: UILayoutPriorityDefaultLow, verticalFittingPriority: UILayoutPriorityDefaultLow)
customViewFrame.size = customViewSize
leftButtonCustomView.frame = customViewFrame
leftButtonCustomView.setNeedsLayout()
leftButtonCustomView.layoutIfNeeded()
}
invalidateIntrinsicContentSize()
}
I am trying to use this plugin as refresh action : https://github.com/entotsu/PullToBounce
One, issue is I can't understand his explanation.
Explanation given on the github
tableView.frame = yourFrame --> tableView is equal to scrollView.frame in my situation
yourFrame --> I have no idea what it is. The main frame ? Another Frame I have to create ?
bodyView.addSubview(tableViewWrapper) --> bodyView ? Main Frame here ? or Another frame ?
Here is my code for the scrollView for now. Any help on how to implement this plugin using a scrollView made via the storyboard.
class ProfileViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
func makeMock() {
let headerView = UIView()
headerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 64)
headerView.backgroundColor = UIColor.lightBlue
self.view.addSubview(headerView)
let headerLine = UIView()
headerLine.frame = CGRect(x: 0, y: 0, width: 120, height: 8)
headerLine.layer.cornerRadius = headerLine.frame.height/2
headerLine.backgroundColor = UIColor.whiteColor().colorWithAlphaComponent(0.8)
headerLine.center = CGPoint(x: headerView.frame.center.x, y: 20 + 44/2)
headerView.addSubview(headerLine)
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blue
let bodyView = UIView()
bodyView.frame = scrollView.frame
bodyView.frame.y += 20 + 44
self.view.addSubview(bodyView)
let tableViewWrapper = PullToBounceWrapper(scrollView: scrollView)
bodyView.addSubview(tableViewWrapper)
tableViewWrapper.didPullToRefresh = {
NSTimer.schedule(delay: 2) { timer in
tableViewWrapper.stopLoadingAnimation()
}
}
makeMock()
}
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
}
One thing, I notice is that there is a View on top of my scrollView that disable me to view it and scroll it. Help here needed please.
Regards,
Hary
Take a look at the Example of this library.
yourFrame is nothing but your tableview class. For example if your tableView Class is named SampleTableView, then it goes like
let tableView = SampleTableView(frame: self.view.frame, style: UITableViewStyle.Plain).
You have to use another class to set up your tableView.
I have tried embedding it, but my stack view is dynamic and my app is also changing orientations time to time. I have segment control at the end of the view.
I have also tried googling it but had no luck. thanks in advance.
So far I have done:
In view did load:
mainStackView.axis = UILayoutConstraintAxis.Vertical
mainStackView.spacing = 3
scrollView.frame = self.view.bounds
scrollView.addSubview(mainStackView)
view.addSubview(scrollView)
In view did layout:
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
let top = topLayoutGuide.length
let bottom = bottomLayoutGuide.length
self.mainStackView.frame = CGRect(x: 0, y: top, width: view.frame.width, height: view.frame.height - top - bottom).insetBy(dx: 10, dy: 10)
dispatch_async(dispatch_get_main_queue())
{
self.scrollView.frame = self.view.bounds
self.scrollView.contentSize = CGSize(width: self.view.bounds.width, height: self.segmentedControl.frame.origin.y + self.segmentedControl.frame.height + 50)
}
print(scrollView.contentSize)
}
You need to set the height constraint of segment control.
For Example:
segmentedControl.heightAnchor.constraintEqualToConstant(50).active = true
More over, you can Add Empty bottom view to avoid stack view's must fill mechanism. This will show you desired view output.
var bottomView = UIView(frame: CGRectZero)
stackView.addArrangedSubview(bottomView)
I would like to achieve the design you see in dating apps. Where you can vertically scroll images of a profile and also horizontally scroll to view the next or previous person in the list.
Currently I have my views laid out as such.
Previous-UIView - current UIView - next UIView
UIScrollView. UIScrollView. UIScrollView
Images. Images. Images
UIView. UIView. UIView
Profile info. Profile info. Profile info
UIPageControl. UIPageControl UIPageControl.
Only one of the Views occupies the main view with next and previous off screen. Ideally when the user moves the view left I would programmatically remove the previous view, make current the previous, the next current and add a new view for next. Visa versa for moving right.
What is the best way to scroll the views horizontally?
Should I wrap them all in a UIScrollView? And would that interfere with the UIScrollView sub Views?
Or should I program touch controls to move the views?
Or is there a better way?
I'm still a newbie at iOS development so any help would be greatly appreciated.
So I've tried some experimenting with a test app and I'm pleased to say you can have UIScrollviews inside UIScrollviews.
I was able to get it running perfectly. Here is my code below.
override func viewDidLoad() {
super.viewDidLoad()
self.superView.delegate = self
// Do any additional setup after loading the view, typically from a nib.
var subImages1 = ["IMG_0004.JPG","IMG_0005.JPG","IMG_0008.JPG"]
var subImages2 = ["IMG_0009.JPG","IMG_0010.JPG","IMG_0011.JPG"]
var subImages3 = ["IMG_0013.JPG","IMG_0017.JPG","IMG_0018.JPG"]
self.images.append(subImages1)
self.images.append(subImages2)
self.images.append(subImages3)
self.superView.frame = self.view.frame
self.superView.contentSize = CGSizeMake(self.view.frame.width*3, self.view.frame.height)
self.superView.contentOffset = CGPoint(x:self.view.frame.width,y:0)
self.superView.pagingEnabled = true
self.view.addSubview(self.superView)
//layout the UIVeiws into the master ScrollView
for i in 0...2{
var offset = self.view.frame.width * CGFloat(i)
var pView = UIView()
pView.frame = CGRectMake(offset, 0, self.view.frame.width, self.view.frame.height)
pView.backgroundColor = colours[i]
self.superView.addSubview(pView)
self.profileViews.append(pView)
}
// Add sub Scroll views and images to the Views.
for (index, view) in enumerate(self.profileViews){
var scrollView = UIScrollView()
scrollView.delegate = self
scrollView.frame = CGRectMake(10, 10, self.view.frame.width-20, self.view.frame.height-20)
scrollView.pagingEnabled = true
scrollView.contentSize = CGSizeMake(scrollView.frame.width, scrollView.frame.height * CGFloat(images[index].count))
for (index2, image) in enumerate(images[index]){
var subImage = UIImageView()
subImage.frame = CGRectMake(0, scrollView.frame.height * CGFloat(index2), scrollView.frame.width, scrollView.frame.height)
subImage.contentMode = UIViewContentMode.ScaleAspectFit
subImage.image = UIImage(named: image as! String)
scrollView.addSubview(subImage)
}
view.addSubview(scrollView)
self.scrollViews.append(scrollView)
}
}
//Use the did end decelerating as it executes the code once the scoll has finished moving.
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
if(scrollView == self.superView){
var contentOffset = scrollView.contentOffset
var pageWidth = self.superView.frame.width
var fractionalPage:Double = Double(self.superView.contentOffset.x / pageWidth)
var page = lround(fractionalPage)
// In this example I take the last UIView from the stack and move it to the first.
// I would do the same in the real app but update the contents of the view after
if(page == 0){
var tempView = self.profileViews[2]
self.profileViews[2].removeFromSuperview()
self.profileViews.removeAtIndex(2)
for view in self.profileViews{
view.frame = CGRectMake(view.frame.minX + self.view.frame.width, 0, view.frame.width, view.frame.height)
println(view.frame)
}
tempView.frame = CGRectMake(0, 0, tempView.frame.width, tempView.frame.height)
self.profileViews.insert(tempView, atIndex: 0)
self.superView.addSubview(tempView)
var newOffset = contentOffset.x + pageWidth
self.superView.contentOffset = CGPoint(x: newOffset, y: 0)
}
// Take the first view and move it to the last.
if(page == 2){
var tempView = self.profileViews[0]
self.profileViews[0].removeFromSuperview()
self.profileViews.removeAtIndex(0)
for view in self.profileViews{
view.frame = CGRectMake(view.frame.minX - self.view.frame.width, 0, view.frame.width, view.frame.height)
println(view.frame)
}
tempView.frame = CGRectMake(tempView.frame.width*2, 0, tempView.frame.width, tempView.frame.height)
self.profileViews.append(tempView)
self.superView.addSubview(tempView)
var newOffset = contentOffset.x - pageWidth
self.superView.contentOffset = CGPoint(x: newOffset, y: 0)
}
}
}
I created a sample app here: https://github.com/steffimueller/LTNavigationBar-TestProject
When you pull down the table at the top above the image a white background appears. This should not be the case. The header image should always be bound at the top of the screen. Here is how it looks in my case:
and here is how it should look like:
The former screenshot is from this app using the parallax branch of the git repo. It is the first tab in the parallax demo which has the effect I want.
Here is the code I use to create the table:
class Page1ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var topView:UIView!
var topImageView: UIImageView?
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.setLeftBarButtonItem(UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Cancel, target: self, action: "sdfsdf"), animated: true)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: cellIndentifier)
self.navigationController?.navigationBar.lt_setBackgroundColor(UIColor.clearColor())
tableView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleWidth
topView = UIView(frame: CGRectMake(0, 0, 320, 212))
topView.contentMode = UIViewContentMode.ScaleToFill
topView.autoresizesSubviews = true
topView.backgroundColor = UIColor.yellowColor()
topView.clipsToBounds = true
tableView.tableHeaderView = topView
let img = UIImage(named: "bg")
topImageView = UIImageView(image: img)
topImageView?.frame = CGRectMake(0, -89, UIScreen.mainScreen().bounds.size.width, 307)
topImageView?.contentMode = UIViewContentMode.ScaleToFill
topView.addSubview(topImageView!)
}
func scrollViewDidScroll(scrollView: UIScrollView) {
let color: UIColor = UIColor(red: 0/255, green: 175/255, blue: 240/255, alpha: 1)
let offsetY:CGFloat = scrollView.contentOffset.y
if offsetY > NAVBAR_CHANGE_POINT {
let alpha:CGFloat = 1 - ((NAVBAR_CHANGE_POINT + 64 - offsetY) / 64)
self.navigationController?.navigationBar.lt_setBackgroundColor(color.colorWithAlphaComponent(alpha))
}
else {
self.navigationController?.navigationBar.lt_setBackgroundColor(color.colorWithAlphaComponent(0))
}
if offsetY < 0 {
let progress:CGFloat = fabs(offsetY) / 300
self.topImageView?.transform = CGAffineTransformMakeScale(1 + progress, 1 + progress)
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tableView.snp_makeConstraints { (make) -> Void in
make.top.equalTo(self.view).offset(-64)
}
}
}
Edit: I don't want to set tableView.bounces = false.
How can I stick the TableHeader Image always on top of the Screen like in my second screenshot?
There are lots of ways to do this.
Do you want the topView to scroll with the table view? If so, instead of adding the topView as a subview, just assign it to the table view's tableHeaderView property.
Don't want it to scroll? Add the topView to the view controller's view, not the table view. Then position the table view below the top view in the nib/storyboard.
Want the table view to scroll over the image view as the user scrolls down? Put the image view behind the table view, make the table view transparent, and make the table view's contentInset start the first cell below the image.
I'm guessing just messing with the contentInset's top value will get you what you want here.
It's not clear why both the topView and the topImageView are needed. It looks like just the topImageView would be sufficient.
Just add a frame to your tableView like this:
self.tableView.frame=CGRectMake(0, YYY, 320, 307)
where YYY is the Y position you want the table to be positioned at
UPDATE:
Based on your updated question, if you want to have the image appear to be "stuck' to the top of the tableview, even when the user pulls the table down, you need to use the bounds of the tableView to set the frame of your image. Use something like this to set the image frame:
UIImageView *myImage = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f - self.tableView.bounds.size.height, self.view.frame.size.width, 100.0f+self.tableView.bounds.size.height)];
[self.tableView addSubview:myImage];
You need to make sure your image is larger then the visible headerView so it will continue to show as the user pulls the table down.
The "100.0f+" part is just adding height to the image so it will show
into the headerView, adjust this to fit your image and tableView
header.
UPDATE 2
- (void)viewDidLoad {
[super viewDidLoad];
self.myTableView.delegate=self;
self.myTableView.dataSource=self;
UIImageView *myImageView=[[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f - self.myTableView.bounds.size.height, self.view.frame.size.width, 300.0F+self.myTableView.bounds.size.height)];
[myImageView setClipsToBounds:TRUE];
[myImageView setImage:[UIImage imageNamed:#"myImage.jpg"]];
[self.myTableView addSubview:myImageView];
}
You have to use the following in viewDidLoad():
topView = UIView(frame: CGRectMake(0, 0, 320, 300))
let testView = UIView(frame: CGRectMake(0, 0, 320, 200))
testView.backgroundColor = UIColor.clearColor()
tableView.tableHeaderView = testView
let img = UIImage(named: "bg")
topImageView = UIImageView(image: img)
topImageView?.frame = CGRectMake(0, 0, 320, 180)
topView.addSubview(topImageView!)
topImageView?.contentMode = UIViewContentMode.ScaleToFill
tableView.addSubview(topView!)
tableView.sendSubviewToBack(topView!)
Try by adding an ImageView just below the UITableView(background color should be clearColor) and setting the
yourTableView.contentInset = UIEdgeInsetsMake(100.0, 0.0, 0.0, 0.0).
If you want to bound the UIImage to top of the screen then do the following things. Disable Bounce property of UITableView. you can disable that property from xib or programmatically as well. here i write the code for disable Bounce property programmatically in swift.
self.tableView.bounces = false
Hope this help you.
What i did in my case is to use tableHeaderView instead of section header like, myTableView.tableHeaderView = myImageView
and then in scrollViewDidScrollMethod,
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.y < 0
{
scrollView.layoutIfNeeded()
var headerFrame = myTableView.tableHeaderView?.frame
headerFrame?.origin.y = scrollView.contentOffset.y
headerFrame?.size.height = headerHeight-scrollView.contentOffset.y //headerHeight is a constant for actual height of header on storyboard
myTableView.tableHeaderView?.frame = headerFrame!
}
}
Hope this helps in your case