I've changed several of the navigationBar properties for a view. However, I'd like to make sure everything reverts back to the default values once the view is unwinded or a new view is shown. Is there a way to reset all values?
Changed Values:
// set status bar white
self.navigationController?.navigationBar.barStyle = UIBarStyle.Default
// make navigation bar clear/transparent
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.translucent = false
let titleDict: NSDictionary = [NSForegroundColorAttributeName: UIColor.whiteColor()]
self.navigationController?.navigationBar.titleTextAttributes = titleDict
If anyone else needs this:
func setNavigationBarDefaults() {
self.navigationController?.navigationBar.barStyle = UIBarStyle.Default
self.navigationController?.navigationBar.translucent = false
}
override func viewWillDisappear(animated: Bool) {
setNavigationBarDefaults()
}
Related
I have to replace an existing SwiftUI List of views with a UICollectionView(because the app designs were updated and the new designs are pretty complex for SwiftUI so it that had to be implemented as a custom UICollectionViewFlowLayout)
So the views(which now will be (or a part of)UICollectionViewCell) are already implemented in SwiftUI, and i didn't want to re-write them in Swift. The view are complex in terms of writing down the Layout Code, which apparently was pretty easy using SwiftUI.
I could find some help wrapping up a collection view like this one but little on how to host an existing swift ui view inside a UICollectionViewCell
Any help/suggestions/links would be appreciated.
Thanks
I did get that working. But not sure if that's the correct solution. If anyone has a better solution please add your answer.
So i created a UICollectionViewCell sub-class.
final class HostingCollectionViewCell: UICollectionViewCell {
func host<Content: View>(_ hostingController: UIHostingController<Content>) {
backgroundColor = .clear
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
hostingController.view.backgroundColor = .clear
addSubview(hostingController.view)
let constraints = [
hostingController.view.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0),
hostingController.view.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 0),
hostingController.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0),
hostingController.view.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: 0),
]
NSLayoutConstraint.activate(constraints)
}
}
And used it like..
func makeUIView(context: Context) -> UICollectionView {
let collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: MyCustomCollectionViewLayout()
)
collectionView.register(HostingCollectionViewCell.self, forCellWithReuseIdentifier: "HostingCell")
let dataSource = UICollectionViewDiffableDataSource<Section, Member>(collectionView: collectionView) { collectionView, indexPath, member in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HostingCell", for: indexPath) as? HostingCollectionViewCell
for subview in cell?.contentView.subviews ?? [] {
subview.removeFromSuperview()
}
let swiftUIView = SomeSwiftUIView()
.onTapGesture {
// Equivalent of didTapItemAt...
}
cell?.host(UIHostingController(rootView: firefighterView))
return cell
}
context.coordinator.dataSource = dataSource
collectionView.clipsToBounds = false
collectionView.backgroundColor = .clear
collectionView.showsVerticalScrollIndicator = false
// Populated Datasource
return collectionView
}
This makeUIView is part of the struct SwiftUICollectionView: UIViewRepresentable view.
So this is kind of a noobish question but I just can't figure out a very simple way to detect currently focused item's indexPath.
I looked around hoping to see something very easy like collectionView.indexPathOfCurrentlyFocusedItem but didn't find anything remotely close.
So I digged around and tried to find something similar at UIFocusEnvironment, UIFocusUpdateContext trying to find the desired property but failed.
So, the only solution I can come up with is just iterating through all visible cells and finding a cell with focused property set to true.
So is there a more simple and elegant way to find the currently focused item's indexPath? (Except tracking it through delegate method and saving it in view controller's property)
You can use UIScreen property focusedView as followed for this:
if let focusedCell = UIScreen.main.focusedView as? UICollectionViewCell {
if let indexPath = collectionView.indexPath(for: focusedCell) {
print("IndexPath is \(indexPath)")
}
}
Use didUpdateFocusInContect - UICollectionViewDelegate
func collectionView(collectionView: UICollectionView, didUpdateFocusInContext context: UICollectionViewFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if collectionView == self.collectionView {
print(context.nextFocusedIndexPath)
}
}
This wil return the indexPath of the cell that is going to be focused, you could also try:
context.previouslyFocusedIndexPath
Depends what you're trying to do.
So, your target is to do something when you get and lose focus particularly on a cell under tvOS. The catch is you're moving around other UI elements, and therefore the context could be different. You have to change in this context only those UIs that you have to care of.
The right place to make your implementation is func didUpdateFocusInContext(), like this:
override func didUpdateFocusInContext(
context: UIFocusUpdateContext,
withAnimationCoordinator coordinator: UIFocusAnimationCoordinator
) {
coordinator.addCoordinatedAnimations({
if let cell = context.previouslyFocusedView as? UICollectionViewCell {
cell.layer.borderWidth = 2
}
if let cell = context.nextFocusedView as? UICollectionViewCell {
cell.layer.borderWidth = 5
}
},
completion: nil)
}
Now we're using the focus coordinator to apply our logic:
When the previously focused item is UICollectionViewCell then you have to release the focus to the next item. You shouldn't care what is the next item because it could be a collection cell or not. For fun, in this case, let's change the border to 2. This value could be set by default.
When the next focused item is UICollectionViewCell then you've to handle it is a similar way, or it will become a mess... So, let's change the border to 5.
As you can see, didUpdateFocusInContext() provides a generic approach for all views within your current visual context. You can apply the same approach for other UI elements.
Have a fun with tvOS...
Here's how I accomplished this in shouldUpdateFocusInContext
Solution
override func shouldUpdateFocusInContext(context: UIFocusUpdateContext) -> Bool {
// The magic is in the next two lines
let cell: UICollectionViewCell = context.nextFocusedView as! UICollectionViewCell
let indexPath: NSIndexPath? = self.collectionView.indexPathForCell(cell)
print(indexPath)
// <NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}
return true
}
i have a table view which contain a label in each row so what i want to do is changing the label backgroundColor and color on user click
but what i get is just changing the color and the background color is changed just on user click and then go back to default color
here is my listener :
s_f_table1.addEventListener("click", function(e) {
e.source.setBackgroundColor("red");
e.source.color = "black";
console.log(e.source.id);
});
If you are using a custom layout try this:
tableView.addEventListener("click", function(e) {
var label = e.row.children[0]; // Label doesn't has to be the first child, depend on your layout.
label.color = "black";
label.backgroundColor = "red";
});
however if you have the default row style then you can setup properties as selectedBackgroundColor, selectedColor or selectionStyle. See the documentation http://docs.appcelerator.com/titanium/latest/#!/api/Titanium.UI.TableViewRow
or you can do following:
tableView.addEventListener("click", function(e) {
e.row.color = "black";
e.row.backgroundColor = "red"; // Whole row
});
Edit: If you want to change a background color don't forget to disable selectionStyle of the row using: selectionStyle: Ti.UI.iPhone.TableViewCellSelectionStyle.NONE
I have done a View in CollectionView with CustomLayout. In iOS6 it worked great but iOS7 it throws an exception like this.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'layout attributes for supplementary item at index path ( {length = 2, path = 0 - 0}) changed from CustomSupplementaryAttributes: 0xd1123a0 index path: (NSIndexPath: 0xd112580 {length = 2, path = 0 - 0}); element kind: (identifier); frame = (0 0; 1135.66 45); zIndex = -1; to CustomSupplementaryAttributes: 0xd583c80 index path: (NSIndexPath: 0xd583c70 {length = 2, path = 0 - 0}); element kind: (identifier); frame = (0 0; 1135.66 45); zIndex = -1; without invalidating the layout'
iOS 10
At iOS 10, a new feature is introduced, it is Cell Prefetching. It will let dynamic position of SupplementaryView crash. In order to run in the old behavior, it needs to disable prefetchingEnabled. It's true by default at iOS 10.
// Obj-C
// This function is available in iOS 10. Disable it for dynamic position of `SupplementaryView `.
if ([self.collectionView respondsToSelector:#selector(setPrefetchingEnabled:)]) {
self.collectionView.prefetchingEnabled = false;
}
// Swift
if #available(iOS 10, *) {
// Thanks #maksa
collectionView.prefetchingEnabled = false
// Swift 3 style
colView.isPrefetchingEnabled = false
}
I hate this problem. I spend 2 days for this problem.
A reference about Cell Pre-fetch #iOS 10.
iOS 9 and before ...
#Away Lin is right.. I solve the same problem by implementing that delegate method.
My Custom UICollectionViewLayout will modify the attributes in layoutAttributesForElementsInRect. The section position is dynamic, not static. So, I obtain warnings about the layout attributes for supplementary item at index path ... changed from ... to .... Before the changes, invalideLayout related methods should be called.
And, after implementing this delegate method to return true, the method invalidateLayoutWithContext: will be called when scrolling the UICollectionViewLayout. By default, it returns false.
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
From Apple Docs
Return Value
true if the collection view requires a layout update or false if the
layout does not need to change.
Discussion The default implementation of this method returns false.
Subclasses can override it and return an appropriate value based on
whether changes in the bounds of the collection view require changes
to the layout of cells and supplementary views.
If the bounds of the collection view change and this method returns
true, the collection view invalidates the layout by calling the
invalidateLayoutWithContext: method.
Availability Available in iOS 6.0 and later.
And more ...
A nice example project on GitHub, for custom UICollectionViewLayout.
You need to invalidate the existing layout before updating, see the end of the error message:
without invalidating the layout'
[collectionViewLayout invalidateLayout];
Apple Documentation for UICollectionViewLayout
I had the same exception: in iOS 7, you need now to override the inherited isEqual: in your UICollectionViewLayoutAttributes subclass as stated in Apple documentation here.
I solved my problem by override the method at the subclase of UICollectionViewFlowLayout:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBound
return YES
I'm not entirely certain how or why, but this appears to be fixed in iOS 12, supporting both supplementary view resizing and prefetching. The trick for me was to make sure things are happening in the correct order.
Here is a working implementation of a stretchable header view. Notice the implementation of the header resizing happening in layoutAttributesForElements(in rect: CGRect):
class StretchyHeaderLayout: UICollectionViewFlowLayout {
var cache = [UICollectionViewLayoutAttributes]()
override func prepare() {
super.prepare()
cache.removeAll()
guard let collectionView = collectionView else { return }
let sections = [Int](0..<collectionView.numberOfSections)
for section in sections {
let items = [Int](0..<collectionView.numberOfItems(inSection: section))
for item in items {
let indexPath = IndexPath(item: item, section: section)
if let attribute = layoutAttributesForItem(at: indexPath) {
cache.append(attribute)
}
}
}
if let header = layoutAttributesForSupplementaryView(ofKind: StretchyCollectionHeaderKind, at: IndexPath(item: 0, section: 0)) {
cache.append(header)
}
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let visibleAttributes = cache.filter { rect.contains($0.frame) || rect.intersects($0.frame) }
guard let collectionView = collectionView else { return visibleAttributes }
// Find the header and stretch it while scrolling.
guard let header = visibleAttributes.filter({ $0.representedElementKind == StretchyCollectionHeaderKind }).first else { return visibleAttributes }
header.frame.origin.y = collectionView.contentOffset.y
header.frame.size.height = headerHeight.home - collectionView.contentOffset.y
header.frame.size.width = collectionView.frame.size.width
return visibleAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItem(at: indexPath as IndexPath)?.copy() as! UICollectionViewLayoutAttributes
guard collectionView != nil else { return attributes }
attributes.frame.origin.y = headerHeight.home + attributes.frame.origin.y
return attributes
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: StretchyCollectionHeaderKind, with: indexPath)
}
override var collectionViewContentSize: CGSize {
get {
guard let collectionView = collectionView else { return .zero }
let numberOfSections = collectionView.numberOfSections
let lastSection = numberOfSections - 1
let numberOfItems = collectionView.numberOfItems(inSection: lastSection)
let lastItem = numberOfItems - 1
guard let lastCell = layoutAttributesForItem(at: IndexPath(item: lastItem, section: lastSection)) else { return .zero }
return CGSize(width: collectionView.frame.width, height: lastCell.frame.maxY + sectionInset.bottom)
}
}
}
P.S.: I'm aware the cache doesn't actually serve any purpose at this point :)
I had this problem too, because I had code that depended on the content size of the collection view. My code was accessing the content size via the collectionView!.contentSize instead of collectionViewContentSize.
The former uses the collectionView property of UICollectionViewLayout, while the latter uses the custom-implemented layout property. In my code, the first time the layout was asked for attributes, contentSize had not been set yet.
Select the CollectionView and Goto Attribute Inspector. Uncheck The Prefetching Enabled CheckBox. This is Fixed my Issue. Screenshot
I want to to send data from one window to another.
Example:
I have a text field and a button on first window. When click on window I need to send text field value to the second window?
I found a tutorial, but it's not in MVC.
I have created a new alloy controller (left click in your project and then new) and here is how i am passing parameters to the next view.
The new controller is called CallBack and the first controller is called index.
In CallBack.xml i have:
<Alloy>
<View class="container">
</View>
</Alloy>
In CallBack.tss i have:
".container": {
backgroundColor: "black"
}
In CallBack.js i have:
var args = arguments[0] || {};
//here you can do whatever you want to your parameter, i just show the value.
alert(args.textField);
And finally in index.js this is how i am passing the parameters of my textField:
//with a button i can open a new view in my current window
$.btnNext.addEventListener('click',function(e){
//tfInsert is the id of my textfield in index.xml file and with .value i can access to whatever it contains
//the "?" operator is like an if
var textField = $.tfInsert.value != "" ? textField = $.tfInsert.value : textField = "Hello";
var nextView = Alloy.createController('/CallBack', {
textField: textField
}).getView();
//this is how i add a new view to my current window
$.window.add(nextView);
});
Hope this helps.
In controller.js (from where we are passing Data)
function createController(win)
{
//title is the data to pass
var platform = Ti.Platform.osname;
var calledWindow = require('/ui/' + platform + '/addProductWindow').createCalledWindow(title);
calledWindow.open();
};
In calledWindowController.js
function createController(win){
//Do whatever control you want
};
In calledWindow.js
exports.createCalledWindow = function(title)
{
//Do whatever UI you want
Ti.include('/Controllers/calledWindowController.js');
var controller = createController(win);
return win;
};