I have two css classes on a tornadofx label bound to a SimpleBooleanProperty. One which has a background image and a blue border and one which has no background image and a yellow border.
Snippet from View containing label:
val switch: SimpleBooleanProperty = SimpleBooleanProperty(false)
label("my label"){
toggleClass(UIAppStyle.style1, switch.not())
toggleClass(UIAppStyle.style2, switch)
}
Snippet from UIAppStyle:
s(style1){
textFill = Color.YELLOW
maxWidth = infinity
maxHeight = infinity
alignment = Pos.CENTER
backgroundImage += this::class.java.classLoader.getResource("img.png")!!.toURI()
backgroundPosition += BackgroundPosition.CENTER
backgroundRepeat += Pair(BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT)
borderColor += box(Color.BLUE)
}
s(style2){
textFill = Color.YELLOW
maxWidth = infinity
maxHeight = infinity
alignment = Pos.CENTER
borderColor += box(Color.YELLOW)
}
When switch = false, there is a background image and a blue border. When switch = true, there is the same background image and a yellow border. I'm not finding out how to get the background image to remove. Interestingly enough, if I add a different background image to style2, it changes correctly.
Edit: To remove two toggleClasses and introduce new strange problem:
class MyView : View(){
...
init{
...
row{
repeat(myviewmodel.numSwitches){
val switch = myviewmodel.switches[it]
val notSwitch = switch.not()
label("my label"){
addClass(UIAppStyle.style2)
toggleClass(UIAppStyle.style1, notSwitch)
}
}
}
}
This code snippet does not work for me. However, if I add private var throwsArray = mutableListOf<ObservableValue<Boolean>>() as a field of MyView and add notSwitch to the array, then the same exact code works. It's almost as if notSwitch is going out of scope and becoming invalidated unless I add it to a local array in the class?
I don’t understand why you want to have two different toggleClass for the same control. As you pointed out, the problem in your case is that when the backgroundImage is set, you need to set a new one in order to change it. But in your case, you only have to add the style without backgroundImage first and them set toggleClass with the style with backgroundImage. Like this:
label("my label"){
addClass(UIAppStyle.style2)
toggleClass(UIAppStyle.style1, switch)
}
button {
action {
switch.value = !switch.value;
}
}
Edit: This ilustrate what I'm talking about in comments:
class Example : View("Example") {
override val root = vbox {
val switch = SimpleBooleanProperty(false)
val notSwitch = switch.not()
label("my label"){
addClass(UIAppStyle.style2)
toggleClass(UIAppStyle.style1, notSwitch)
}
button("One") {
action {
switch.value = !switch.value;
}
}
button("Two") {
action {
notSwitch.get()
}
}
}
}
You can put the notSwitch.get() in any action and without trigger that action it does the work. Check how I put it in the action of button Two, but without clicking that button even once, it works.
This is actually some kind of hack, in order to achieve what you want. But I don’t see the reason why my initial solution with true as default value for property shouldn’t work.
Edited to do inverse of status
Here is simple example of a working toggle class using your styling:
class TestView : View() {
override val root = vbox {
val status = SimpleBooleanProperty(false)
label("This is a label") {
addClass(UIAppStyle.base_cell)
val notStatus = SimpleBooleanProperty(!status.value)
status.onChange { notStatus.value = !it } // More consistent than a not() binding for some reason
toggleClass(UIAppStyle.smiling_cell, notStatus)
}
button("Toggle").action { status.value = !status.value }
}
init {
importStylesheet<UIAppStyle>()
}
}
As you can see, the base class is added as the default, while styling with the image is in the toggle class (no not() binding). Like mentioned in other comments, the toggleClass is picky, additive in nature, and quiet in failure so it can sometimes be confusing.
FYI I got to this only by going through your github code and I can say with confidence that the not() binding is what screwed you in regards to the toggleClass behaviour. Everything else causing an error is related to other problems with the code. Feel free to ask in the comments or post another question.
Related
In the following TornadoFX/Kotlin code
import javafx.scene.control.TreeItem
import javafx.scene.control.TreeView
import tornadofx.*
class MyObj {
var type : Int = 0
constructor(type : Int) {
this.type = type
}
}
class MainView: View("Minimal TV demo") {
var treeRoot : TreeItem<MyObj> = TreeItem()
var objectsTreeView : TreeView<MyObj>? = null
override val root = vbox {
objectsTreeView = treeview(treeRoot) {
showRootProperty().value = false
cellFormat {
if(it.type == 0) {
text = "Test"
graphic = null
}
else {
text = null
graphic = vbox {
label("Label 1")
button("123")
textarea {
prefWidth = 100.0
prefHeight = 125.0
}
}
}
}
}
}
init {
with (root) {
for(i in 1..10) {
val x = TreeItem(MyObj(0))
treeRoot.children.add(x)
x.children.add(TreeItem(MyObj(1)))
}
}
}
}
when opening a few of the tree items and scrolling the tree view, the tree view contents and slider seems to act rather "jumpy", i.e. when I move the slider down, the mouse moves but the slider and content stay where it is, until the mouse gets so far down, then the content jumps. It just doesn't feel or look good.
I believe I could get around this by adding separate TreeItem's for each UI element row, but is there a way to achieve a smooth scroll without doing this? I tried suing a fixed cell height, which seems to work, but of course this doesn't look right at all given that some rows a shorter than others.
Don't use cellFormat for complex tree UIs, since the elements will be recreated very rapidly and most probably cause flicker. Your best bet is to create a subclass of TreeCellFragment and configured it using the cellFragment call.
Now you can create the UI only once per Tree cell, and reuse it when the data it represents changes. This is much more performant.
I have a UICollectionView in my tvOS app that has a image and a caption text. I've setup a constraint to pin the caption text just under the image.
When the cell is focused and the images grows, the text does not move and stays over the image. Is there a way to set a constraint that takes in account the focused image size?
You can use a UIImageView property focusedFrameGuide for that. It returns a UILayoutGuide object, which unfortunately can't be used in Interface Builder, but you can create constraints with it in code. Note that this constraint makes sense only when the view is focused, so you have to set its active property according to the self.focused property.
First create the constraint on view initialization:
self.focusedSpacingConstraint = NSLayoutConstraint(item: imageView.focusedFrameGuide, attribute: .BottomMargin, relatedBy: .Equal, toItem: label, attribute: .Top, multiplier: 1, constant: 0)
//add it to the view and set active to false
Then activate either this constraint or the default one depending on the focus:
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
coordinator.addCoordinatedAnimations({
self.focusedSpacingConstraint.active = self.focused
self.spacingConstraint.active = !self.focused
//set label's transform and animate layout changes
}
You can also use the focusedFrameGuide to set label's height (as a percentage of image's height).
The advantage of this approach is that you don't have to change the (hardcoded) constant whenever the image size changes.
I ended up setting a #IBOutlet to my spacing constraint and I update it in the didUpdateFocusInContext method. To add some effect, I also transform the label.
In my collection cell:
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
coordinator.addCoordinatedAnimations({
if self.focused {
self.spacingConstraint.constant = 30
UIView.animateWithDuration(0.3) {
self.label.transform = CGAffineTransformMakeScale(1.15, 1.15)
self.layoutIfNeeded()
}
}
else {
self.spacingConstraint.constant = 0
UIView.animateWithDuration(0.3) {
self.label.transform = CGAffineTransformMakeScale(1, 1)
self.layoutIfNeeded()
}
}
}, completion: nil)
}
Use this code, I hope it help you. This code write into the custom cell class.
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if (self.focused) {
self.custom_ImageView.adjustsImageWhenAncestorFocused = true
self.custom_LabelView1.frame = CGRectMake(0, 279, 548, 29)
}
else {
self.custom_ImageView.adjustsImageWhenAncestorFocused = false
self.custom_LabelView1.frame = CGRectMake(0, 259, 548, 29)
}
}
Short version:
I am having a problem with auto layout top layout guide when used in conjunction with custom transition and UINavigationController in iOS7. Specifically, the constraint between the top layout guide and the text view is not being honored. Has anyone encountered this issue?
Long version:
I have a scene which has unambiguously define constraints (i.e. top, bottom, left and right) that renders a view like so:
But when I use this with a custom transition on the navigation controller, the top constraint to the top layout guide seems off and it renders is as follows, as if the top layout guide was at the top of the screen, rather than at the bottom of the navigation controller:
It would appear that the "top layout guide" with the navigation controller is getting confused when employing the custom transition. The rest of the constraints are being applied correctly. And if I rotate the device and rotate it again, everything is suddenly rendered correctly, so it does not appear to be not a matter that the constraints are not defined properly. Likewise, when I turn off my custom transition, the views render correctly.
Having said that, _autolayoutTrace is reporting that the UILayoutGuide objects suffer from AMBIGUOUS LAYOUT, when I run:
(lldb) po [[UIWindow keyWindow] _autolayoutTrace]
But those layout guides are always reported as ambiguous whenever I look at them even though I've ensured that there are no missing constraints (I've done the customary selecting of view controller and choosing "Add missing constraints for view controller" or selecting all of the controls and doing the same for them).
In terms of how precisely I'm doing the transition, I've specified an object that conforms to UIViewControllerAnimatedTransitioning in the animationControllerForOperation method:
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController*)fromVC
toViewController:(UIViewController*)toVC
{
if (operation == UINavigationControllerOperationPush)
return [[PushAnimator alloc] init];
return nil;
}
And
#implementation PushAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
CGFloat width = fromViewController.view.frame.size.width;
toViewController.view.transform = CGAffineTransformMakeTranslation(width, 0);
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.transform = CGAffineTransformMakeTranslation(-width / 2.0, 0);
toViewController.view.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
#end
I've also done a rendition of the above, setting the frame of the view rather than the transform, with the same result.
I've also tried manually make sure that the constraints are re-applied by calling layoutIfNeeded. I've also tried setNeedsUpdateConstraints, setNeedsLayout, etc.
Bottom line, has anyone successfully married custom transition of navigation controller with constraints that use top layout guide?
Managed to fix my issue by adding this line:
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
To:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC fromView:(UIView *)fromView toView:(UIView *)toView {
// Add the toView to the container
UIView* containerView = [transitionContext containerView];
[containerView addSubview:toView];
[containerView sendSubviewToBack:toView];
// animate
toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration animations:^{
fromView.alpha = 0.0;
} completion:^(BOOL finished) {
if ([transitionContext transitionWasCancelled]) {
fromView.alpha = 1.0;
} else {
// reset from- view to its original state
[fromView removeFromSuperview];
fromView.alpha = 1.0;
}
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
From Apple's Documentation for [finalFrameForViewController] : https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewControllerContextTransitioning_protocol/#//apple_ref/occ/intfm/UIViewControllerContextTransitioning/finalFrameForViewController:
I solved this by fixing the height constraint of the topLayoutGuide. Adjusting edgesForExtendedLayout wasn't an option for me, as I needed the destination view to underlap the navigation bar, but also to be able to layout subviews using topLayoutGuide.
Directly inspecting the constraints in play shows that iOS adds a height constraint to the topLayoutGuide with value equal to the height of the navigation bar of the navigation controller. Except, in iOS 7, using a custom animation transition leaves the constraint with a height of 0. They fixed this in iOS 8.
This is the solution I came up with to correct the constraint (it's in Swift but the equivalent should work in Obj-C). I've tested that it works on iOS 7 and 8.
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view
let destinationVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
destinationVC.view.frame = transitionContext.finalFrameForViewController(destinationVC)
let container = transitionContext.containerView()
container.addSubview(destinationVC.view)
// Custom transitions break topLayoutGuide in iOS 7, fix its constraint
if let navController = destinationVC.navigationController {
for constraint in destinationVC.view.constraints() as [NSLayoutConstraint] {
if constraint.firstItem === destinationVC.topLayoutGuide
&& constraint.firstAttribute == .Height
&& constraint.secondItem == nil
&& constraint.constant == 0 {
constraint.constant = navController.navigationBar.frame.height
}
}
}
// Perform your transition animation here ...
}
I struggled with the exact same problem. Putting this in the viewDidLoad of my toViewController really helped me out:
self.edgesForExtendedLayout = UIRectEdgeNone;
This did not solve all my issues and I'm still looking for a better approach, but this certainly made it a bit easier.
Just put the following code toviewDidLoad
self.extendedLayoutIncludesOpaqueBars = YES;
FYI, I ended up employing a variation of Alex's answer, programmatically changing the top layout guide's height constraint constant in the animateTransition method. I'm only posting this to share the Objective-C rendition (and eliminate the constant == 0 test).
CGFloat navigationBarHeight = toViewController.navigationController.navigationBar.frame.size.height;
for (NSLayoutConstraint *constraint in toViewController.view.constraints) {
if (constraint.firstItem == toViewController.topLayoutGuide
&& constraint.firstAttribute == NSLayoutAttributeHeight
&& constraint.secondItem == nil
&& constraint.constant < navigationBarHeight) {
constraint.constant += navigationBarHeight;
}
}
Thanks, Alex.
As #Rob mentioned, topLayoutGuide is not reliable when using custom transitions in UINavigationController. I worked around this by using my own layout guide. You can see the code in action in this demo project. Highlights:
A category for custom layout guides:
#implementation UIViewController (hp_layoutGuideFix)
- (BOOL)hp_usesTopLayoutGuideInConstraints
{
return NO;
}
- (id<UILayoutSupport>)hp_topLayoutGuide
{
id<UILayoutSupport> object = objc_getAssociatedObject(self, #selector(hp_topLayoutGuide));
return object ? : self.topLayoutGuide;
}
- (void)setHp_topLayoutGuide:(id<UILayoutSupport>)hp_topLayoutGuide
{
HPLayoutSupport *object = objc_getAssociatedObject(self, #selector(hp_topLayoutGuide));
if (object != nil && self.hp_usesTopLayoutGuideInConstraints)
{
[object removeFromSuperview];
}
HPLayoutSupport *layoutGuide = [[HPLayoutSupport alloc] initWithLength:hp_topLayoutGuide.length];
if (self.hp_usesTopLayoutGuideInConstraints)
{
[self.view addSubview:layoutGuide];
}
objc_setAssociatedObject(self, #selector(hp_topLayoutGuide), layoutGuide, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
HPLayoutSupport is the class that will act as a layout guide. It has to be a UIView subclass to avoid crashes (I wonder why this isn't part of the UILayoutSupport interface).
#implementation HPLayoutSupport {
CGFloat _length;
}
- (id)initWithLength:(CGFloat)length
{
self = [super init];
if (self)
{
self.translatesAutoresizingMaskIntoConstraints = NO;
self.userInteractionEnabled = NO;
_length = length;
}
return self;
}
- (CGSize)intrinsicContentSize
{
return CGSizeMake(1, _length);
}
- (CGFloat)length
{
return _length;
}
#end
The UINavigationControllerDelegate is the one responsible for "fixing" the layout guide before the transition:
- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
toVC.hp_topLayoutGuide = fromVC.hp_topLayoutGuide;
id <UIViewControllerAnimatedTransitioning> animator;
// Initialise animator
return animator;
}
Finally, the UIViewController uses hp_topLayoutGuide instead of topLayoutGuide in the constraints, and indicates this by overriding hp_usesTopLayoutGuideInConstraints:
- (void)updateViewConstraints
{
[super updateViewConstraints];
id<UILayoutSupport> topLayoutGuide = self.hp_topLayoutGuide;
// Example constraint
NSDictionary *views = NSDictionaryOfVariableBindings(_imageView, _dateLabel, topLayoutGuide);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[topLayoutGuide][_imageView(240)]-8-[_dateLabel]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views];
[self.view addConstraints:constraints];
}
- (BOOL)hp_usesTopLayoutGuideInConstraints
{
return YES;
}
Hope it helps.
i found way. First uncheck "Extend Edges" property of controller. after that navigation bar getting dark color. Add a view to controller and set top and bottom LayoutConstraint -100. Then make view's clipsubview property no (for navigaionbar transculent effect). My english bad sory for that. :)
I had the same problem, ended up implementing my own topLayout guide view and making constraints to it rather then to topLayoutGuide. Not ideal. Only posting it here in case someone is stuck and looking for quick hacky solution http://www.github.com/stringcode86/SCTopLayoutGuide
Here's the simple solution I'm using that's working great for me: during the setup phase of - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext, manually set your "from" and "to" viewController.view.frame.origin.y = navigationController.navigationBar.frame.size.height. It'll make your auto layout views position themselves vertically as you expect.
Minus the pseudo-code (e.g. you probably have your own way of determining if a device is running iOS7), this is what my method looks like:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
CGAffineTransform destinationTransform;
UIViewController *targetVC;
CGFloat adjustmentForIOS7AutoLayoutBug = 0.0f;
// We're doing a view controller POP
if(self.isViewControllerPop)
{
targetVC = fromViewController;
[container insertSubview:toViewController.view belowSubview:fromViewController.view];
// Only need this auto layout hack in iOS7; it's fixed in iOS8
if(_device_is_running_iOS7_)
{
adjustmentForIOS7AutoLayoutBug = toViewController.navigationController.navigationBar.frame.size.height;
[toViewController.view setFrameOriginY:adjustmentForIOS7AutoLayoutBug];
}
destinationTransform = CGAffineTransformMakeTranslation(fromViewController.view.bounds.size.width,adjustmentForIOS7AutoLayoutBug);
}
// We're doing a view controller PUSH
else
{
targetVC = toViewController;
[container addSubview:toViewController.view];
// Only need this auto layout hack in iOS7; it's fixed in iOS8
if(_device_is_running_iOS7_)
{
adjustmentForIOS7AutoLayoutBug = toViewController.navigationController.navigationBar.frame.size.height;
}
toViewController.view.transform = CGAffineTransformMakeTranslation(toViewController.view.bounds.size.width,adjustmentForIOS7AutoLayoutBug);
destinationTransform = CGAffineTransformMakeTranslation(0.0f,adjustmentForIOS7AutoLayoutBug);
}
[UIView animateWithDuration:_animation_duration_
delay:_animation_delay_if_you_need_one_
options:([transitionContext isInteractive] ? UIViewAnimationOptionCurveLinear : UIViewAnimationOptionCurveEaseOut)
animations:^(void)
{
targetVC.view.transform = destinationTransform;
}
completion:^(BOOL finished)
{
[transitionContext completeTransition:([transitionContext transitionWasCancelled] ? NO : YES)];
}];
}
A couple of bonus things about this example:
For view controller pushes, this custom transition slides the pushed toViewController.view on top of the unmoving fromViewController.view. For pops, fromViewController.view slides off to the right and reveals an unmoving toViewController.view under it. All in all, it's just a subtle twist on the stock iOS7+ view controller transition.
The [UIView animateWithDuration:...] completion block shows the correct way to handle completed & cancelled custom transitions. This tiny tidbit was a classic head-slap moment; hope it helps somebody else out there.
Lastly, I'd like to point out that as far as I can tell, this is an iOS7-only issue that has been fixed in iOS8: my custom view controller transition that is broken in iOS7 works just fine in iOS8 without modification. That being said, you should verify that this is what you're seeing too, and if so, only run the fix on devices running iOS7.x. As you can see in the code example above, the y-adjustment value is 0.0f unless the device is running iOS7.x.
I ran into this same issue but without using a UINavigationController and just positioning a view off of the topLayoutGuide. The layout would be correct when first displayed, a transition would take place to another view, and then upon exiting and returning to the first view, the layout would be broken as that topLayoutGuide would no longer be there.
I solved this problem by capturing the safe area insets prior to the transition and then reimplementing them, not by adjusting my constraints, but by setting them on the viewController's additionalSafeAreaInsets.
I found this solution to work well as I don't have to adjust any of my layout code and search through constraints and I can just reimplementing the space that was there previously. This could be more difficult if you are actually using the additionalSafeAreaInsets property.
Example
I added a variable to my transitionManager to capture the safe insets that exist when the transitionManager is created.
class MyTransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
private var presenting = true
private var container:UIView?
private var safeInsets:UIEdgeInsets?
...
Then during the entering transition I save those insets.
let toView = viewControllers.to.view
let fromView = viewControllers.from.view
if #available(iOS 11.0, *) {
safeInsets = toView.safeAreaInsets
}
In the case of the iPhone X this looks something like UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)
Now when exiting, the insets on that same view we transitioned from in the entrance will be .zero so we add our captured insets to the additionalSafeAreaInsets on the viewController, which will set them on our view for us as well as update the layout. Once our animation is done, we reset the additionalSafeAreaInsets back to .zero.
if #available(iOS 11.0, *) {
if safeInsets != nil {
viewControllers.to.additionalSafeAreaInsets = safeInsets!
}
}
...then in the animation completion block
if #available(iOS 11.0, *) {
if self.safeInsets != nil {
viewControllers.to.additionalSafeAreaInsets = .zero
}
}
transitionContext.completeTransition(true)
try :
self.edgesforextendedlayout=UIRectEdgeNone
Or just set navigationbar opaque and set background image or backgroundcolor to navigationbar
In storyboard add another vertical constraint to main view's top. I have the same problem too but adding that constraint help me to avoid manual constraints. See screenshot here link
Other solution is to calculate toVC frame... something like this:
float y = toVC.navigationController.navigationBar.frame.origin.y + toVC.navigationController.navigationBar.frame.size.height;
toVC.view.frame = CGRectMake(0, y, toVC.view.frame.size.width, toVC.view.frame.size.height - y);
Let me know if you have found a better solution. I have been struggling with this issue as well and I came up with previous ideas.
I'm using a list element with variableRowHeight and word-wrap set to true like in the example below:
http://blog.flexexamples.com/2007/10/27/creating-multi-line-list-rows-with-variable-row-heights/
When I scroll through the list with a mouse scrollwheel the text in the listItems also scroll. I know that this is to do with the height of the textField... but I am unsure how to best resolve this issue. Any thoughts are appreciated!
The issue that I am experiencing also occurs in the example above.
OK, solved it after a bit of work. Thought I would put it here for others:
This can be simply solved by extending the List element, and the ListItemRenderer and modifying a couple of lines:
First up, extend the List element:
package au.com.keeghan.controls {
import mx.controls.List;
import mx.core.ClassFactory;
public class ExtendedList extends List{
public function ExtendedList(){
super();
itemRenderer = new ClassFactory(ExtendedListItemRenderer);
}
}
}
Now we want to extend the newly created item renderer (ExtendedListItemRenderer). Because there isn't actually that much code required, we can just put it in the same .as file. We do this by declaring it as a internal class, and locating it outside of the package above... just below the closing bracket:
import mx.controls.listClasses.ListItemRenderer;
internal class AsOneListItemRenderer extends ListItemRenderer{
override protected function measure():void{
super.measure();
var w:Number = 0;
if (icon)
w = icon.measuredWidth;
// Guarantee that label width isn't zero
// because it messes up ability to measure.
if (label.width < 4 || label.height < 4)
{
label.width = 4;
label.height = 16;
}
if (isNaN(explicitWidth))
{
w += label.getExplicitOrMeasuredWidth();
measuredWidth = w;
measuredHeight = label.getExplicitOrMeasuredHeight();
}
else
{
measuredWidth = explicitWidth;
label.setActualSize(Math.max(explicitWidth - w, 4), label.measuredHeight + 3);
label.validateNow();
label.height = label.textHeight + 5;
measuredHeight = label.getExplicitOrMeasuredHeight() + 3;
if (icon && icon.measuredHeight > measuredHeight){
measuredHeight = icon.measuredHeight;
}
}
}
}
Now, the majority of the above code is actually just copied from the ListItemRenderer, the magic occurs down the bottom... specifically these lines:
label.setActualSize(Math.max(explicitWidth - w, 4), label.measuredHeight + 3);
label.validateNow();
label.height = label.textHeight + 5;
measuredHeight = label.getExplicitOrMeasuredHeight() + 3;
All I do here is add some height to both the label, and the overall measuredHeight which in the end is the thing that is causing this issue.
The only downside to this solution is that you will get a larger amount of padding below the listItem element, however you can still make this look good by playing around with the verticalAlign and padding css properties.
I've been playing around with different methods of determining at runtime the width of a "label" so that I can resize the "label" because I don't want it to truncate. I've finally found an easy solution through UITextField which allows me to set the .autoSize which is great! However, now I'm trying to "style" (simply adjust font and font size) of the UITextField but it seems that I have to do it manually with '.htmlText' (which I'll gladly accept if that is the ONLY way).
I'm using the .text to set the value of the label.
My test case involves a HBox (I'm actually using a Grid but they should be the same and I've done testing on both):
I style the HBox and the style carries through to the UITextField. I don't believe this will work for me because I have other components inside that I need to style differently.
I've tried: UITextFormat and TextFormat (I see that the .htmlText being updated accordingly but the output doesn't update. Then I noticed that whenever I called hbox.addChild(myUITextField) it would override the .htmlText
I've tried setting the style with myUITextField.setStyle("fontSize", 20) before and/or after the call to addChild neither of which made an impact on the display as per what I noted above.
Changes are being made but they seem to be overrided when I add it to the display.
So what do I need to do in order to style the UITextField aside from manually setting it along with my contents in .htmlText? Solutions not using UITextField is fine as long as there is some easy way of not truncating the text.
EDIT: I want to just do textField.setStyle('fontSize', 20) and expect that every time I change the text, I wouldn't need to use HTML to go with it (so I can just do textField.text = 'something else' and expect that it will still have a font size of 20). This is what I meant by not using .htmlText (sorry if I wasn't clear before).
2nd EDIT: I guess I should present the whole issue and maybe that'll clarify what I did wrong or couldn't achieve.
My intent is to have a Grid and add text into it. I do not want it to wrap or scroll so I add it to the next row in the Grid when the current row's children total width exceeds some number. In order to add it to the next row, I need to be able to calculate the width of the text. I would like to be able to style that text individually based on cases and there might be other components (like a TextInput). Essentially what I'm trying to accomplish is "Fill in the Blank".
I've included code to show what I'm currently doing and it works somewhat. It might be un-related to the original issue of styling but I can't figure out how to adjust the distance between each UITextField but aside from that this fits what I would like to accomplish. Relevant to the question is: I would like to change the way I style each UITextField (currently setting .htmlText) into something a bit straightforward though like I previously mentioned I'll gladly accept using .htmlText if that's the only solution.
So I have a Grid with x Rows in it and in each row, I have exactly one GridItem. Based on the input, I add UITextField and TextInput into the GridItem going on to the next GridItem when necessary. If you have a better way of doing so then that would be better but I guess what I really want is to find a different way of styling.
Also another problem, I'm not sure of the exact way to add a TextField into the display. I tried:
var t : TextField = new TextField();
t.text = "I'm a TextField";
hBox.addChild(t); // doesn't work
//this.addChild(t); // doesn't work either
But I get the following error:
TypeError: Error #1034: Type Coercion failed: cannot convert flash.text::TextField#172c8f9 to mx.core.IUIComponent.
Here's what I have that's working.
private function styleQuestionString(str : String) : String {
return '<FONT leading="1" face="verdana" size="20">' + str + '</FONT>';
}
private function loadQuestion(str : String) : void {
/* Split the string */
var tmp : Array = str.split("_");
/* Track the current width of the GridItem */
var curWidth : int = 0;
/* Display components that we will add */
var txtField : UITextField = null;
var txtInput : TextInput = null;
/* Track the current GridItem */
var curGridItem : GridItem = null;
/* Track the GridItem we can use */
var gridItemAC : ArrayCollection = new ArrayCollection();
var i : int = 0;
/* Grab the first GridItem from each GridRow of Grid */
var tmpChildArray : Array = questionGrid.getChildren();
for (i = 0; i < tmpChildArray.length; i++) {
gridItemAC.addItem((tmpChildArray[i] as GridRow).getChildAt(0));
}
curGridItem = gridItemAC[0];
gridItemAC.removeItemAt(0);
/* Used to set the tab index of the TextInput */
var txtInputCounter : int = 1;
var txtFieldFormat : UITextFormat = new UITextFormat(this.systemManager);
txtFieldFormat.leading = "1";
//var txtFieldFormat : TextFormat = new TextFormat();
//txtFieldFormat.size = 20;
/* Proper Order
txtField = new UITextField();
txtField.text = tmp[curItem];
txtField.autoSize = TextFieldAutoSize.LEFT;
txtField.setTextFormat(txtFieldFormat);
*/
var txtLineMetrics : TextLineMetrics = null;
var tmpArray : Array = null;
curGridItem.setStyle("leading", "1");
var displayObj : DisplayObject = null;
for (var curItem : int= 0; curItem < tmp.length; curItem++) {
/* Using UITextField because it can be auto-sized! */
/** CORRECT BLOCK (ver 1)
txtField = new UITextField();
txtField.text = tmp[curItem];
txtField.autoSize = TextFieldAutoSize.LEFT;
txtField.setTextFormat(txtFieldFormat);
***/
tmpArray = (tmp[curItem] as String).split(" ");
for (i = 0; i < tmpArray.length; i++) {
if (tmpArray[i] as String != "") {
txtField = new UITextField();
txtField.htmlText = styleQuestionString(tmpArray[i] as String);
//txtField.setTextFormat(txtFieldFormat); // No impact on output
txtLineMetrics = curGridItem.measureHTMLText(txtField.htmlText);
curWidth += txtLineMetrics.width + 2;
if (curWidth >= 670) {
curGridItem = gridItemAC[0];
curGridItem.setStyle("leading", "1");
if (gridItemAC.length != 1) {
gridItemAC.removeItemAt(0);
}
// TODO Configure the proper gap distance
curWidth = txtLineMetrics.width + 2;
}
displayObj = curGridItem.addChild(txtField);
}
}
//txtField.setColor(0xFF0000); // WORKS
if (curItem != tmp.length - 1) {
txtInput = new TextInput();
txtInput.tabIndex = txtInputCounter;
txtInput.setStyle("fontSize", 12);
txtInputCounter++;
txtInput.setStyle("textAlign", "center");
txtInput.width = TEXT_INPUT_WIDTH;
curWidth += TEXT_INPUT_WIDTH;
if (curWidth >= 670) {
curGridItem = gridItemAC[0];
if (gridItemAC.length != 1) {
gridItemAC.removeItemAt(0);
}
// TODO Decide if we need to add a buffer
curWidth = TEXT_INPUT_WIDTH + 2;
}
curGridItem.addChild(txtInput);
txtInputAC.addItem(txtInput);
/* Adds event listener so that we can perform dragging into the TextInput */
txtInput.addEventListener(DragEvent.DRAG_ENTER, dragEnterHandler);
txtInput.addEventListener(DragEvent.DRAG_DROP, dragDropHandler);
txtInput.addEventListener(DragEvent.DRAG_EXIT, dragExitHandler);
}
/* Add event so that this label can be dragged */
//txtField.addEventListener(MouseEvent.MOUSE_MOVE, dragThisLabel(event, txtField.text));
}
}
After about 8 hours of searching for a solution to what would seem to be such a simple issue I FINALLY stumbled on your posts here... Thankyou!!!
I have been stumbling around trying to get TextField to work and had no Joy, Label was fine, but limited formatting, and I need to be able to use embedded fonts and rotate. After reading the above this finally worked for me:
var myFormat:TextFormat = new TextFormat();
myFormat.align = "center";
myFormat.font = "myFont";
myFormat.size = 14;
myFormat.color = 0xFFFFFF;
var newTxt:UITextField = new UITextField();
newTxt.text = "HELLO";
addChild(newTxt);
newTxt.validateNow();
newTxt.setTextFormat(myFormat);
The order of addChild before the final 2 steps was critical! (myFont is an embedded font I am using).
One again... a thousand thankyou's...
John
EDIT BASED ON THE ASKERS FEEDBACK:
I didn't realize you wanted to just apply one style to the whole textfield, I thought you wanted to style individual parts. This is even simpler for you, won't give you any trouble at all :)
var textFormat: TextFormat = new TextFormat("Arial", 12, 0xFF0000);
myText.setTextFormat(textFormat);
Be aware that this sets the style to the text that is in the TextField, not necessarily future text you put in. So have your text in the field before you call setTextFormat, and set it again every time you change it just to be sure it stays.
It's probably best if you use a normal TextField as opposed to the component. If you still want the component you may need to call textArea.validateNow() to get it to update with the new style (not 100% sure on that one though) Adobe components are notoriously bad, and should be avoided. :(
To see all available options on the TextFormat object see here
END EDIT ---------
This is easy enough to just do with CSS in a normal old TextField.
var myCSS: String = "Have some CSS here, probably from a loaded file";
var myHTML: String = "Have your HTML text here, and have it use the CSS styles";
// assuming your textfield's name is myText
var styleSheet: StyleSheet = new StyleSheet();
styleSheet.parseCSS(myCSS);
myText.autoSize = TextFieldAutoSize.LEFT;
myText.styleSheet = styleSheet;
myText.htmlText = myHTML;
Supported HTML tags can be found here
Supported CSS can be found here
The reason you have a problem adding Textfield to containers is that it doesn't implement the IUIComponent interface. You need to use UITextField if you want to add it. However, that's presenting me with my own styling issues that brought me to this question.
A few things I know:
TextField is styled using the TextFormat definition, and applying it to the textfield. As Bryan said, order matters.
setStyle does nothing on IUITextField, and the TextFormat method doesn't seem to work the same as in normal TextFields. (Edit #2: Ahah. You need to override the "validateNow" function on UITextFields to use the setTextFormat function)
To autosize a TextArea, you need to do something like this (inheriting from TextArea):
import mx.core.mx_internal;
use namespace mx_internal;
...
super.mx_internal::getTextField().autoSize = TextFieldAutoSize.LEFT;
this.height = super.mx_internal::getTextField().height;
Found this code on, I think, on StackOverflow a while back. Apologies to the original author. But the idea is that you need to access the "mx_internal" raw textfield.
Text and TextArea have wrapping options. (Label does not). So if you set the explicit width of a Text object, you might be able to size using the measuredHeight option and avoid truncation.
(edit: That was #4, but stackoverflow parsed it into a 1...)