SWIFTUI Tabview makes me long press after embedding in UiHostController, how do I stop this? - tabview

I am adding a SwiftUi view to my story board via UIHostController. The integration works and I have full functionality of buttons and image taps, however the Tabview at the bottom of the screen requires a long press in order for the Tabview to work and switch for me. If I run the same SwiftUI file within a swift application (no uikit, nor uihostcontroller) it works as intended. It has to be something with the UIhostcontroller or the way I am wrapping the swiftui view. Again the tabview does work but it would take someone forever to figure out you have to press and hold the button to get the page to switch. Anyone know what might be going on?
import UIKit
import SwiftUI
import FirebaseAuth
import FirebaseAuthUI
class ViewController: UIViewController {
let contentView = UIHostingController(rootView: ContentView())
override func viewDidLoad(){
super.viewDidLoad()
// Do any additional setup after loading the view.
addChild(contentView)
view.addSubview(contentView.view)
setupConstraints()
}
fileprivate func setupConstraints(){
contentView.view.translatesAutoresizingMaskIntoConstraints=false
contentView.view.topAnchor.constraint(equalTo:view.topAnchor).isActive=true
contentView.view.bottomAnchor.constraint(equalTo:view.bottomAnchor).isActive=true
contentView.view.leftAnchor.constraint(equalTo:view.leftAnchor).isActive=true
contentView.view.rightAnchor.constraint(equalTo:view.rightAnchor).isActive
= true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}

Related

NavigationLink button focusable override issue

I face an issue which stucks for days. I am createing a tvos application which reqiures a custome navigationlink(button), when I move the focus to the navigation item, it should scale a little bit, and also I need to change the parent's view backgound. It is pretty simple, but it seems that the focusabe override the my custome button Style. The test shows that the background image was changed but without any scale effect when the navigationbutton get focused. Any suggestion?
NavigationLink(destination: Text("myview"))
{Text("Test")
}
.buttonStyle(myButtonStyle())
.Focusable(true){(focus) in
//the code to change the background image
//myButtonStyle definition
struct MyButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
return AppButton(configuration: configuration)
}
}
struct AppButton: View {
#Environment(\.isFocused) var focused: Bool
let configuration: ButtonStyle.Configuration
var body: some View {
configuration.label
.scaleEffect(focused ? 1.1 : 1.0)
.focusable(true)
}
}
The line to change the background image is always called when the item get focused as my expected, but the scale effect is gone. If I remove the following line of codes, the scale effect is back:
// .Focusable(true){(focus) in
//the code to change the background image
// }
It looks like that this line of code override my custome style of navigation button, any ideas? Appreciate any help!
Ah, finally I found the tricky, though there is very little document about this. When Focusable was introduced, it should not be in your code to change focus engine, which will cause the navigationlink tap message uncaptured, then your navigationlink for another view will not work.
Use .onChange() function to deal with any focus change event, not use Focusable.

How can I use SwiftUI to populate content in a UICollectionViewCell

Because SwiftUI has no UICollectionView-like controls,so I used UIKit instead.
According to interfacing-with-uikit, I know how SwiftUI interacts with UIKit, but I want to further use SwiftUI to encapsulate UICollectionViewCell, and use SwiftUI to fill the content of UICollectionViewCell, how can I do that.
As far as I know List in SwiftUI are encapsulated according to UITableView, I want SwiftUI to do the same with UICollectionView,I don't want to use some tripartite libraries to do this, I want to learn the secrets
Assuming you have CellContentView SwiftUI view, you can integrate it in UICollectionViewCell using something like the following
if let cellContent = UIHostingController(rootView: CellContentView()).view {
cell.contentView.addSubview(cellContent)
cellContent.leadingAnchor.constraint(equalTo: cell.leadingAnchor).isActive = true
cellContent.trailingAnchor.constraint(equalTo: cell.trailingAnchor).isActive = true
cellContent.topAnchor.constraint(equalTo: cell.topAnchor).isActive = true
cellContent.bottomAnchor.constraint(equalTo: cell.bottomAnchor).isActive = true
}

How to hide the iOS 8 Today Widget when there is nothing to display?

I'm using the today widget for iOS8 to display information relevant to the current day. The problem is I don't want to display the widget/section at all if there are no relevant messages to show.
I know it must be possible as the BA app does it (it only shows the widget when there is a flight, the rest of the time its not visible at all). I just cant figure out a way to achieve this behaviour.
Does anyone know how this can be done?
I found the way to do this is using the NCWidgetController. This allows you to easily specify when the today widget should be displayed based on whatever criteria you see fit.
Simply add the following into your viewDidLoad method (or anywhere that will be called when the widget reloads) in the today widget view controller and it will work:
BOOL hasDataToDisplay = NO;
NCWidgetController *widgetController = [NCWidgetController widgetController];
[widgetController setHasContent:hasDataToDisplay forWidgetWithBundleIdentifier:#"com.my-company.my-app.my-widget"];
Apple Docs: https://developer.apple.com/library/ios/documentation/NotificationCenter/Reference/NCWidgetController_Class/index.html#//apple_ref/occ/cl/NCWidgetController
WARNING: The NCWidgetController cannot be reset from the widget itself once you have set there is no content to display. In other words once you set it to NO then there is no going back unless you trigger it from the parent/containing app.
In the widget's ViewController's viewDidLoad method add the following:
BOOL DisplayWidget = NO;
[[NCWidgetController widgetController] setHasContent:DisplayWidget
forWidgetWithBundleIdentifier:#"<widget's bunder identifier>"];
This will disable the widget from showing.
To enable it again, you must do that from the containing app using the same line passing YES to setHasContent parameter. Make sure to add the necessary imports to the containing app in the ViewController which will re-enable the widget:
#import <NotificationCenter/NotificationCenter.h>
#interface ViewController () <NCWidgetProviding> {...}
[Check out page 41 of the documentations for widgets
https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensibilityPG.pdf ]
The approach which I used, though not perfect and has a small remnant in Notification Center, but worked for me:
In viewDidLoad() set preferred content size height to 1:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view from its nib.
preferredContentSize = CGSizeMake(0, 1)
view.setNeedsLayout()
}
then when widget updates, gets real height and set it:
var data: NSData?
func updateData() {
// fetch data
if let data = data {
let viewHeight: CGFloat
// UI preperation and initialize viewHeight var
preferredContentSize = CGSizeMake(0, viewHeight);
} else {
preferredContentSize = CGSizeMake(0, 1);
}
}
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
updateData()
completionHandler(data != nil ? NCUpdateResult.NewData : NCUpdateResult.NoData)
}
It is better use
+ (instancetype)widgetController
then call
- (void)setHasContent:(BOOL)flag forWidgetWithBundleIdentifier:(NSString *)bundleID

Disable scroll for PDFView inside NSCollectionView

I have a PdfView inside a CollectionView. Since both have its own scrollviews I have conflicts in scrolling. So i want to disable scrolling for PdfView. How can I do it?
My solution is a little rough, but does work.
I only wanted to stop horizontal scrolling on my PDFViews - my collection view scrolls horizontally.
I made a view that selectively filters out the scroll mouse events and put it over the PFD view.
class HorizontalScrollBlockerView: NSView
{
var scrollNextResponder: NSResponder?
override func scrollWheel(with event: NSEvent) {
guard scrollNextResponder != nil else {
return
}
if fabs(event.deltaX) >= fabs(event.deltaY) {
scrollNextResponder!.scrollWheel(with: event)
} else {
super.scrollWheel(with: event)
}
}
}
I set the view's 'scrollNextResponder' to be the superView of the PDFView.
I also wrote a method that gets the first child scroll view (enclosedScrollView) which makes sure the PDFView is solid in the horizontal axis when correctly scaled.
if let scrollView = pdfView.enclosedScrollView {
scrollView.usesPredominantAxisScrolling = true
scrollView.hasHorizontalScroller = false
scrollView.horizontalScrollElasticity = NSScrollView.Elasticity.none
}
For a regular Scroll View you can remove scrolls by setting horizontal & vertical scrolls to false value. So for a PDF view try this :
NSScrollView *enclosingScrollView = [myPdfView enclosingScrollView];
[enclosingScrollView setHasHorizontalScroller:NO];
[enclosingScrollView setHasVerticalScroller:NO];

How to toggle visibility of NSSplitView subView + hide Pane Splitter divider?

We have a parent Split view (NSSplitView), and two subviews, Content and SideBar (the sidebar is on the right).
What would be the optimal Cocoa-friendly way to toggle the SideBar view?
I would really love it, if the suggested solution includes animation
I really don't need any suggestions related to external plugins, etc (e.g. BWToolkit)
HINT : I've been trying to do that, but still I had issues hiding the divider of the NSSplitView as well. How could I do it, while hiding it at the same time?
Here's a pretty decent tutorial that shows how to do this: Unraveling the Mysteries of NSSplitView.
Hiding the divider is done in NSSplitView's delegate method splitView:shouldHideDividerAtIndex:.
You will have to animate the frame size change yourself if you don't like the way NSSplitView does it.
Easiest way to do it is as follows - and it's animated: [SWIFT 5]
splitViewItems[1].animator().isCollapsed = true // Show side pane
splitViewItems[1].animator().isCollapsed = false // hide side pane
I wrote a Swift version of the content in the link from #Nathan's answer that works for me. In the context of my example splitView is set elsewhere, probably as an instance property on an encompassing class:
func toggleSidebar () {
if splitView.isSubviewCollapsed(splitView.subviews[1] as NSView) {
openSidebar()
} else {
closeSidebar()
}
}
func closeSidebar () {
let mainView = splitView.subviews[0] as NSView
let sidepanel = splitView.subviews[1] as NSView
sidepanel.hidden = true
let viewFrame = splitView.frame
mainView.frame.size = NSMakeSize(viewFrame.size.width, viewFrame.size.height)
splitView.display()
}
func openSidebar () {
let sidepanel = splitView.subviews[1] as NSView
sidepanel.hidden = false
let viewFrame = splitView.frame
sidepanel.frame.size = NSMakeSize(viewFrame.size.width, 200)
splitView.display()
}
These functions will probably methods in a class, they are for me. If your splitView can be nil you obviously have to check for that. This also assumes you have two subviews and the one at index 1, here as sidePanel is the one you want to collapse.
In Xcode 9.0 with Storyboards open Application Scene select View->Menu->Show sidebar. CTRL-click Show Sidebar, in sent actions delete the provided one, click on x. From the circle CTRL drag to First Responder in application scene and select toggleSideBar to connect to. Open storyboard and select the first split view item and in attributes inspector change behaviour from default to sidebar. Run and try with view menu item show/hide. All done in interface builder no code. toggleSideBar handles the first split view item. https://github.com/Dis3buted/SplitViewController
I got some artifacts with the code above, likely because it was out of context. I am sure it works where it was meant to. Anyway, here is a very streamlined implementation:
// this is the declaration of a left vertical subview of
// 'splitViewController', which is the name of the split view's outlet
var leftView: NSView {
return self.splitViewController.subviews[0] as NSView
}
// here is the action of a button that toggles the left vertical subview
// the left subview is always restored to 100 pixels here
#IBAction func someButton(sender: AnyObject) {
if splitViewController.isSubviewCollapsed(leftView) {
splitViewController.setPosition(100, ofDividerAtIndex: 0)
leftView.hidden = false
} else {
splitViewController.setPosition(0, ofDividerAtIndex: 0)
leftView.hidden = true
}
}
To see a good example using animations, control-click to download this file.
If your NSSplitView control is part of a NSSplitViewController object, then you can simply use this:
splitViewController.toggleSidebar(nil)