ইউআইবাটন: হিট অঞ্চলটি ডিফল্ট হিট অঞ্চলের চেয়ে বড় করা


188

ইউআইবাটন এবং এর হিট অঞ্চল নিয়ে আমার একটি প্রশ্ন রয়েছে। আমি ইন্টারফেস বিল্ডারে ইনফ ডার্ক বোতামটি ব্যবহার করছি তবে আমি খুঁজে পাচ্ছি যে হিট অঞ্চলটি কিছু লোকের আঙ্গুলের পক্ষে যথেষ্ট পরিমাণে বড় নয়।

ইনফোবটন গ্রাফিকের আকার পরিবর্তন না করে প্রোগ্রামিয়ালি বা ইন্টারফেস বিল্ডারে কোনও বোতামের হিট অঞ্চল বাড়ানোর কোনও উপায় আছে কি?


যদি কিছুই কাজ না করে, কেবল একটি চিত্র ছাড়াই একটি ইউআইবাটন যোগ করুন এবং তারপরে একটি চিত্রভিউ ওভারলে রাখুন!
বরুণ

এটি পুরো সাইটের সবচেয়ে আশ্চর্যজনক সাধারণ প্রশ্ন হতে হবে, যার কয়েক ডজন উত্তর রয়েছে। আমার অর্থ, আমি এখানে পুরো উত্তরটি পেস্ট করতে পারি: override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.insetBy(dx: -20, dy: -20).contains(point) }
ফ্যাটি

উত্তর:


137

যেহেতু আমি একটি পটভূমি চিত্র ব্যবহার করছি, এর সমাধানগুলির কোনওটিই আমার পক্ষে ভাল কাজ করে না। এখানে এমন একটি সমাধান রয়েছে যা কিছু মজাদার উদ্দেশ্য-সি যাদু করে এবং ন্যূনতম কোড সহ দ্রবণে ড্রপ দেয়।

প্রথমে UIButtonহিট পরীক্ষাকে ওভাররাইড করে এমন কোনও বিভাগ যুক্ত করুন এবং হিট পরীক্ষার ফ্রেমকে প্রসারিত করার জন্য একটি সম্পত্তি যুক্ত করুন।

UIButton + + Extensions.h

@interface UIButton (Extensions)

@property(nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

UIButton + + Extensions.m

#import "UIButton+Extensions.h"
#import <objc/runtime.h>

@implementation UIButton (Extensions)

@dynamic hitTestEdgeInsets;

static const NSString *KEY_HIT_TEST_EDGE_INSETS = @"HitTestEdgeInsets";

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)];
    objc_setAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = objc_getAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS);
    if(value) {
        UIEdgeInsets edgeInsets; [value getValue:&edgeInsets]; return edgeInsets;
    }else {
        return UIEdgeInsetsZero;
    }
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }

    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);

    return CGRectContainsPoint(hitFrame, point);
}

@end

এই শ্রেণিটি যুক্ত হয়ে গেলে, আপনাকে যা করতে হবে তা হ'ল আপনার বোতামের প্রান্ত ইনসেটগুলি সেট করা। নোট করুন যে আমি পোকামাকড় যুক্ত করতে বেছে নিয়েছি তাই আপনি যদি হিট অঞ্চলটি আরও বড় করতে চান তবে আপনাকে অবশ্যই নেতিবাচক সংখ্যা ব্যবহার করতে হবে।

[button setHitTestEdgeInsets:UIEdgeInsetsMake(-10, -10, -10, -10)];

দ্রষ্টব্য: #import "UIButton+Extensions.h"আপনার ক্লাসে ( ) বিভাগটি আমদানি করতে ভুলবেন না।


26
বিভাগে কোনও পদ্ধতিকে ওভাররাইড করা একটি খারাপ ধারণা। অন্য বিভাগ যদি পয়েন্টকে ওভাররাইড করার চেষ্টা করে তবে ইনসাইড:? এবং কলটি [সুপার পয়েন্টআইনসাইড: উইথ এভেন্ট] কল করছে না ইউআইবাটনের সংস্করণের কলটির (যদি এটি থাকে) তবে ইউআইবাটন এর পিতামাতার সংস্করণ।
হনুস

@ সোনাস আপনি ঠিক বলেছেন এবং অ্যাপল এটি করার পরামর্শ দিচ্ছে না। বলা হচ্ছে, যতক্ষণ না এই কোডটি কোনও ভাগ করা লাইব্রেরিতে না থাকে এবং আপনার আবেদনে কেবল স্থানীয় থাকে, আপনার ভাল হওয়া উচিত।
চেজ

হাই চেস, এর পরিবর্তে সাবক্লাস ব্যবহারের কোনও অসুবিধা আছে কি?
সিড

3
আপনি অত্যধিক অপ্রয়োজনীয় কাজ করছেন, আপনি কোডের দুটি সহজ লাইন করতে পারেন: [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal];এবং আপনার প্রয়োজনীয় প্রস্থ এবং উচ্চতা নির্ধারণ করুন: B backButton.frame = সিজিআরেক্টমেক (5, 28, 45, 30); `
রেস্ট

7
@ রিস্টি, তিনি ব্যাকগ্রাউন্ড ইমেজটি ব্যবহার করছেন, এর অর্থ হল আপনার পদ্ধতির কাজ হবে না।
ম্যাটসসন

77

ইন্টারফেস বিল্ডারে কেবল চিত্র প্রান্তের ইনসেট মানগুলি সেট করুন।


1
আমি ভেবেছিলাম এটি আমার পক্ষে কাজ করবে না কারণ পটভূমি চিত্রগুলি পোকামাকড়ের প্রতিক্রিয়া জানায় না তবে আমি চিত্রের বৈশিষ্ট্যটি সেট করতে পরিবর্তিত হয়েছি এবং তারপরে আমার শিরোনামটির জন্য চিত্রের প্রস্থের জন্য একটি নেতিবাচক বাম প্রান্ত ইনসেট সেট করেছি এবং এটি আবার শীর্ষে এসে গেছে আমার ছবি
ডেভ রস

12
এটি কোডেও করা যেতে পারে। উদাহরণস্বরূপ,button.imageEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
bengoesboom

ডেভ রস এর উত্তর দুর্দান্ত। যারা ছবিটি দেখতে পারেন না তাদের জন্য-চিত্রটি আপনার শিরোনামকে নিজের ডানদিকে চাপিয়ে দেয় (স্পষ্টতই), যাতে আপনি এটি ফিরে সরাতে আইবি-তে সামান্য ক্লিককারীকে ক্লিক করতে পারেন (বা কোড অনুসারে বর্ণিত হয়েছে)। কেবলমাত্র নেতিবাচক আমি দেখতে পাচ্ছি যে আমি প্রসারিতযোগ্য চিত্র ব্যবহার করতে পারি না। আইএমও প্রদানের জন্য স্বল্প মূল্য
পল ব্রুনিউ

7
ছবিটি প্রসারিত না করে এটি কীভাবে করবেন?
জোনাবি

কেন্দ্রের মতো কিছুতে সামগ্রী মোড সেট করুন এবং কোনও স্কেলিং নয়।
স্যামুয়েল গুডউইন

63

সুইফটে এক্সটেনশনগুলি ব্যবহার করে এখানে একটি দুর্দান্ত সমাধান দেওয়া হয়েছে। এটি অ্যাপল এর হিউম্যান ইন্টারফেস গাইডলাইনস অনুসারে কমপক্ষে 44x44 পয়েন্টের সমস্ত ইউআইবিটানকে হিট অঞ্চল দেয় ( https://developer.apple.com/ios/human-interface-guidlines/visual-design/layout/ )

সুইফট 2:

private let minimumHitArea = CGSizeMake(44, 44)

extension UIButton {
    public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        // if the button is hidden/disabled/transparent it can't be hit
        if self.hidden || !self.userInteractionEnabled || self.alpha < 0.01 { return nil }

        // increase the hit frame to be at least as big as `minimumHitArea`
        let buttonSize = self.bounds.size
        let widthToAdd = max(minimumHitArea.width - buttonSize.width, 0)
        let heightToAdd = max(minimumHitArea.height - buttonSize.height, 0)
        let largerFrame = CGRectInset(self.bounds, -widthToAdd / 2, -heightToAdd / 2)

        // perform hit test on larger frame
        return (CGRectContainsPoint(largerFrame, point)) ? self : nil
    }
}

সুইফট 3:

fileprivate let minimumHitArea = CGSize(width: 100, height: 100)

extension UIButton {
    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // if the button is hidden/disabled/transparent it can't be hit
        if self.isHidden || !self.isUserInteractionEnabled || self.alpha < 0.01 { return nil }

        // increase the hit frame to be at least as big as `minimumHitArea`
        let buttonSize = self.bounds.size
        let widthToAdd = max(minimumHitArea.width - buttonSize.width, 0)
        let heightToAdd = max(minimumHitArea.height - buttonSize.height, 0)
        let largerFrame = self.bounds.insetBy(dx: -widthToAdd / 2, dy: -heightToAdd / 2)

        // perform hit test on larger frame
        return (largerFrame.contains(point)) ? self : nil
    }
}

3
বোতামটি বর্তমানে লুকানো আছে কিনা তা পরীক্ষা করতে হবে। যদি তাই হয়, শূন্য ফিরে।
জোশ গাফনি

ধন্যবাদ, এটি দুর্দান্ত দেখাচ্ছে কোন সম্ভাব্য ডাউনসাইড সচেতন হতে হবে?
ক্র্যাশলোট

আমি এই সমাধানটি পছন্দ করি, আমাকে সমস্ত বোতামে অতিরিক্ত সম্পত্তি সেট করার দরকার নেই। ধন্যবাদ
xtrinch

1
যদি অ্যাপল নির্দেশিকা হিট অঞ্চলের জন্য এই মাত্রাগুলির প্রস্তাব দেয় তবে আমাদের প্রয়োগে তাদের ব্যর্থতা কেন ঠিক করতে হবে? এটি কি পরবর্তীকালে এসডিকে?
নুডলঅফডিথ

2
আমাদের স্ট্যাক ওভারফ্লো এবং এর অবদানকারীদের সদ্ব্যবহারের জন্য ধন্যবাদ। কারণ আমরা তাদের সবচেয়ে সাধারণ, মৌলিক সমস্যাগুলি কীভাবে ঠিক করতে পারি তার দরকারী, সময়োপযোগী, সঠিক তথ্যের জন্য অবশ্যই অ্যাপলের উপর নির্ভর করতে পারি না।
Womble

49

আপনি সাবক্লাস UIButtonবা একটি কাস্টম UIViewএবং point(inside:with:)এমন কিছু দিয়ে ওভাররাইডও করতে পারেন:

সুইফট 3

override func point(inside point: CGPoint, with _: UIEvent?) -> Bool {
    let margin: CGFloat = 5
    let area = self.bounds.insetBy(dx: -margin, dy: -margin)
    return area.contains(point)
}

উদ্দেশ্য গ

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGFloat margin = 5.0;
    CGRect area = CGRectInset(self.bounds, -margin, -margin);
    return CGRectContainsPoint(area, point);
}

1
কোন অতিরিক্ত বিভাগ ছাড়াই ধন্যবাদ সত্যিই দুর্দান্ত সমাধান। এছাড়াও আমি এক্সআইবিতে থাকা আমার বোতামগুলির সাথে এটি সহজভাবে সংহত করি এবং এটি ভাল কাজ করে। দুর্দান্ত উত্তর
মাতরোসভ আলেকজান্ডার

এটি একটি দুর্দান্ত সমাধান যা অন্য সমস্ত নিয়ন্ত্রণে বাড়ানো যেতে পারে! আমি উদাহরণস্বরূপ একটি ইউআইএসবারবার সাবক্লাস করা দরকারী বলে মনে করি। আইওএস ২.০ থেকে পয়েন্টআইনসাইড পদ্ধতিটি প্রতিটি ইউআইভিউতে রয়েছে। বিটিডাব্লু - সুইফট: ফানক পয়েন্টআইনসাইড (_ পয়েন্ট: সিজিপয়েন্ট, উইন্ডেন্ট ইভেন্ট: ইউআইইভেন্ট!) -> বুল।
টমি সি।

এর জন্য ধন্যবাদ! অন্যরা যেমন বলেছে, এটি অন্যান্য নিয়ন্ত্রণের সাথে সত্যই কার্যকর হতে পারে!
ইয়াঞ্চি

এটা অসাধারণ!! ধন্যবাদ, আপনি আমাকে অনেক সময় বাঁচিয়েছেন! :-)
ভোজটা

35

এখানে চেইসের ইউআইবাটন + এক্সটেনশনগুলি সুইফট 3.0 এ রয়েছে।


import UIKit

private var pTouchAreaEdgeInsets: UIEdgeInsets = .zero

extension UIButton {

    var touchAreaEdgeInsets: UIEdgeInsets {
        get {
            if let value = objc_getAssociatedObject(self, &pTouchAreaEdgeInsets) as? NSValue {
                var edgeInsets: UIEdgeInsets = .zero
                value.getValue(&edgeInsets)
                return edgeInsets
            }
            else {
                return .zero
            }
        }
        set(newValue) {
            var newValueCopy = newValue
            let objCType = NSValue(uiEdgeInsets: .zero).objCType
            let value = NSValue(&newValueCopy, withObjCType: objCType)
            objc_setAssociatedObject(self, &pTouchAreaEdgeInsets, value, .OBJC_ASSOCIATION_RETAIN)
        }
    }

    open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        if UIEdgeInsetsEqualToEdgeInsets(self.touchAreaEdgeInsets, .zero) || !self.isEnabled || self.isHidden {
            return super.point(inside: point, with: event)
        }

        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.touchAreaEdgeInsets)

        return hitFrame.contains(point)
    }
}

এটি ব্যবহার করতে, আপনি:

button.touchAreaEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)

1
আসলে, আমাদের প্রসারিত করা উচিত UIControl, না UIButton
ভ্যালেনটিন শেরগিন

2
এবং পরিবর্তনশীল pTouchAreaEdgeInsetsহতে হবে Int, না UIEdgeInsets। (প্রকৃতপক্ষে, এটি যে কোনও প্রকারের হতে পারে তবে Intএটি সবচেয়ে সাধারণ এবং সাধারণ ধরণের, সুতরাং এটি নির্মাণে এটি সাধারণ)। (অতএব আমি এই পদ্ধতিটি সাধারণভাবে পছন্দ করি Thank আপনাকে ধন্যবাদ, ডিসি রে!)
ভ্যালেন্টিন শেরগিন

1
আমি এডজিনসেটস, ইমেজএডিজিটস, কন্টেন্টএডজিনসেট সব চেষ্টা করেছি আমার জন্য এই এক্সটেনশনটি ভালভাবে কাজ করছে। এই পোস্ট করার জন্য আপনাকে ধন্যবাদ !! দারূন কাজ !!
যোগেশ প্যাটেল

19

আমি আপনার তথ্য বোতামের উপর নির্ভর করে কাস্টম টাইপযুক্ত একটি ইউআইবাটন রাখার পরামর্শ দিচ্ছি। আপনি হিট অঞ্চলটি যে আকারে চান সেটি পছন্দ অনুযায়ী কাস্টম বোতামটিকে পুনরায় আকার দিন। সেখান থেকে আপনার দুটি বিকল্প রয়েছে:

  1. কাস্টম বোতামটির 'হাইলাইটে টাচ দেখান' বিকল্পটি পরীক্ষা করুন। তথ্যের বোতামের উপরে সাদা আভা প্রদর্শিত হবে তবে বেশিরভাগ ক্ষেত্রে ব্যবহারকারীদের আঙুলটি এটি coverেকে রাখবে এবং তারা যা দেখতে পাবে তা হ'ল বাইরের চারপাশে lowজ্জ্বল্য।

  2. তথ্য বোতামের জন্য একটি আইবিআউটলেট এবং কাস্টম বোতামের জন্য দুটি 'আইবিএ'একশন' টাচ ডাউন 'এর জন্য এবং একটি' টাচ আপ ইনসাইড 'এর জন্য সেট আপ করুন। তারপরে এক্সকোডে টাচডাউন ইভেন্টটি তথ্য বোতামের হাইলাইট করা সম্পত্তিটি হ্যাঁ হিসাবে সেট করে এবং টাচআপিনসাইড ইভেন্টটি হাইলাইট করা সম্পত্তিটি NO তে সেট করে।


19

backgroundImageআপনার ইমেজ দিয়ে সম্পত্তি সেট করবেন না, সম্পত্তি সেট করুন imageView। এছাড়াও, আপনি imageView.contentModeসেট করেছেন তা নিশ্চিত করুন UIViewContentModeCenter


1
আরও সুনির্দিষ্টভাবে, বোতামের সামগ্রীর মোড সম্পত্তিটি ইউআইভিউউকন্টেন্টমোড সেন্টারে সেট করুন। তারপরে আপনি বোতামটির ফ্রেমটি চিত্রের চেয়ে বড় করতে পারেন। আমার জন্য কাজ কর!
আরলোমেডিয়া

: অবগতির জন্য, UIButton UIViewContentMode শ্রদ্ধা করে না stackoverflow.com/a/4503920/361247
এনরিকো Susatyo

@ এনারিকো সুস্যাতিও দুঃখিত, আমি কোন এপিআইয়ের কথা বলছিলাম তা স্পষ্ট করে দিয়েছি। imageView.contentModeযেমন নির্ধারণ করা যাবে UIContentModeCenter
মৌরিজিও

এটি নিখুঁতভাবে কাজ করে, পরামর্শের জন্য ধন্যবাদ! [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal];
বিশ্রামের

@ রাস্তি উপরে উল্লিখিত মন্তব্যটি আমার পক্ষে কাজ করে না: / চিত্রটি বড় আকারের ফ্রেমের আকারকে আকার দিচ্ছে।
অনিল

14

সুইফট 3 এ আমার সমাধান:

class MyButton: UIButton {

    override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        let relativeFrame = self.bounds
        let hitTestEdgeInsets = UIEdgeInsetsMake(-25, -25, -25, -25)
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets)
        return hitFrame.contains(point)
    }
}

12

উপস্থাপিত উত্তরগুলির সাথে কোনও ভুল নেই; তবে আমি জারজারারের উত্তরটি প্রসারিত করতে চেয়েছিলাম কারণ এতে আশ্চর্যজনক সম্ভাবনা রয়েছে যা অন্যান্য নিয়ন্ত্রণের সাথে একই সমস্যাটির মূল্য দিতে পারে (যেমন সার্চবার)। এটি কারণ যেহেতু পয়েন্টআইনসাইডটি কোনও ইউআইভিউয়ের সাথে সংযুক্ত, তাই স্পর্শের অঞ্চলটি উন্নত করতে যে কোনও নিয়ন্ত্রণ সাবক্লাস করতে সক্ষম । এই উত্তরটি সম্পূর্ণ সমাধানটি কীভাবে বাস্তবায়ন করতে পারে তার একটি সম্পূর্ণ নমুনা দেখায়।

আপনার বোতামের জন্য একটি নতুন সাবক্লাস তৈরি করুন (বা কোনও নিয়ন্ত্রণ)

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

পরবর্তী আপনার সাবক্লাস বাস্তবায়নে পয়েন্টআইনসাইড পদ্ধতিটি ওভাররাইড করুন

@implementation MNGButton


-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    //increase touch area for control in all directions by 20
    CGFloat margin = 20.0;
    CGRect area = CGRectInset(self.bounds, -margin, -margin);
    return CGRectContainsPoint(area, point);
}


@end

আপনার স্টোরিবোর্ড / এক্সিব ফাইলটিতে প্রশ্নে নিয়ন্ত্রণ নির্বাচন করুন এবং পরিচয় পরিদর্শকটি খুলুন এবং আপনার কাস্টম শ্রেণীর নামে টাইপ করুন।

mngbutton

আপনার ইউআইভিউকন্ট্রোলার ক্লাসে বোতামটি ধারণ করার জন্য, বোতামটির শ্রেণীর ধরণটি আপনার সাবক্লাসের নামে পরিবর্তন করুন।

@property (weak, nonatomic) IBOutlet MNGButton *helpButton;

আপনার স্টোরিবোর্ড / এক্সিব বোতামটি আইবিআউটলেট বৈশিষ্ট্যের সাথে যুক্ত করুন এবং সাবক্লাসে সংজ্ঞায়িত অঞ্চলটি ফিট করার জন্য আপনার স্পর্শ অঞ্চলটি প্রসারিত হবে।

সিজিআরেক্টইনসেট এবং সিজিআরেক্ট কনটেনসপয়েন্ট পদ্ধতিগুলির সাথে একত্রে পয়েন্টআইনসাইড পদ্ধতিটি ওভাররাইড করা ছাড়াও , কোনও ইউআইভিউ সাবক্লাসের আয়তক্ষেত্রাকার স্পর্শের ক্ষেত্রটি বাড়ানোর জন্য সিজিজিওট্রিটি পরীক্ষা করতে সময় নেওয়া উচিত । এছাড়াও আপনি CGGeometry ব্যবহার-ক্ষেত্রে কিছু চমৎকার টিপস খুঁজে পেতে পারেন NSHipster

উদাহরণস্বরূপ, কেউ উপরে বর্ণিত পদ্ধতিগুলি ব্যবহার করে স্পর্শ অঞ্চলটিকে অনিয়মিত করে তুলতে পারে বা প্রস্থের স্পর্শ অঞ্চলটিকে অনুভূমিক স্পর্শের ক্ষেত্রের দ্বিগুণ করে তুলতে পছন্দ করতে পারে:

CGRect area = CGRectInset(self.bounds, -(2*margin), -margin);

নোট: যে কোনও ইউআই ক্লাস নিয়ন্ত্রণ স্থাপনের ক্ষেত্রে বিভিন্ন নিয়ন্ত্রণের (বা কোনও ইউআইভিউ সাবক্লাস, যেমন ইউআইআইমেজভিউ ইত্যাদির জন্য) স্পর্শের ক্ষেত্রটি বাড়ানোর ক্ষেত্রে একই ফলাফল পাওয়া উচিত।


10

এটি আমার পক্ষে কাজ করে:

UIButton *button = [UIButton buttonWithType: UIButtonTypeCustom];
// set the image (here with a size of 32 x 32)
[button setImage: [UIImage imageNamed: @"myimage.png"] forState: UIControlStateNormal];
// just set the frame of the button (here 64 x 64)
[button setFrame: CGRectMake(xPositionOfMyButton, yPositionOfMyButton, 64, 64)];

3
এটি কাজ করে তবে আপনার যদি একটি বোতামে একটি চিত্র এবং শিরোনাম উভয়ই সেট করতে হয় তবে সাবধান হন। সেক্ষেত্রে আপনাকে সেটব্যাকগাউন্ড ইমেজ ব্যবহার করতে হবে যা আপনার চিত্রটিকে বোতামের নতুন ফ্রেমে স্কেল করবে।
মিহাই দামিয়ান

7

ইউআইবাটনের আচরণ পরিবর্তন করবেন না।

@interface ExtendedHitButton: UIButton

+ (instancetype) extendedHitButton;

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

@end

@implementation ExtendedHitButton

+ (instancetype) extendedHitButton {
    return (ExtendedHitButton *) [ExtendedHitButton buttonWithType:UIButtonTypeCustom];
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect relativeFrame = self.bounds;
    UIEdgeInsets hitTestEdgeInsets = UIEdgeInsetsMake(-44, -44, -44, -44);
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

@end

যদি আপনার বোতামটি 40x40 হয় তবে methodণাত্মক সংখ্যা বেশি হলে এই পদ্ধতিটি কল করা হবে না - যেমন অন্য কারও উল্লেখ করা হয়েছে। অন্যথায় এটি কাজ করে।
জেমস ও'ব্রায়েন

এটি আমার পক্ষে কাজ করে না। হিটফ্রেম যথাযথভাবে গণনা করা হয়, তবে পয়েন্টইনসাইড: যখন পয়েন্টটি সেলফাউন্ডসের বাইরে থাকে তখন কখনই তাকে ডাকা হয় না। যদি (সিজিআরেক্ট কনটেনসপয়েন্ট) (হিটফ্রেম, পয়েন্ট) &&CGRectContainsPoint (স্ব.বাউন্ডস, পয়েন্ট)) S এনএসলগ (@ "সাফল্য!"); // কখনই বলা হয় না}
আর্সেনিয়াস

7

আমি swizzling দ্বারা আরো একটি জেনেরিক পদ্ধতির ব্যবহার করছি -[UIView pointInside:withEvent:]। এটি আমাকে হিট টেস্টিং আচরণটি সংশোধন করার অনুমতি দেয় UIView, শুধু নয় UIButton

প্রায়শই একটি ধারক দৃশ্যের ভিতরে একটি বোতাম রাখা হয় যা হিট পরীক্ষারও সীমাবদ্ধ করে। উদাহরণস্বরূপ, যখন কোনও বোতামটি একটি ধারক দৃশ্যের শীর্ষে থাকে এবং আপনি স্পর্শের লক্ষ্যটিকে উপরের দিকে প্রসারিত করতে চান, আপনাকে ধারক দৃশ্যের স্পর্শ লক্ষ্যকেও প্রসারিত করতে হবে।

@interface UIView(Additions)
@property(nonatomic) UIEdgeInsets hitTestEdgeInsets;
@end

@implementation UIView(Additions)

+ (void)load {
    Swizzle(self, @selector(pointInside:withEvent:), @selector(myPointInside:withEvent:));
}

- (BOOL)myPointInside:(CGPoint)point withEvent:(UIEvent *)event {

    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || self.hidden ||
       ([self isKindOfClass:UIControl.class] && !((UIControl*)self).enabled))
    {
        return [self myPointInside:point withEvent:event]; // original implementation
    }
    CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.hitTestEdgeInsets);
    hitFrame.size.width = MAX(hitFrame.size.width, 0); // don't allow negative sizes
    hitFrame.size.height = MAX(hitFrame.size.height, 0);
    return CGRectContainsPoint(hitFrame, point);
}

static char hitTestEdgeInsetsKey;
- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    objc_setAssociatedObject(self, &hitTestEdgeInsetsKey, [NSValue valueWithUIEdgeInsets:hitTestEdgeInsets], OBJC_ASSOCIATION_RETAIN);
}

- (UIEdgeInsets)hitTestEdgeInsets {
    return [objc_getAssociatedObject(self, &hitTestEdgeInsetsKey) UIEdgeInsetsValue];
}

void Swizzle(Class c, SEL orig, SEL new) {

    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);

    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
        method_exchangeImplementations(origMethod, newMethod);
}
@end

এই পদ্ধতির সুন্দর জিনিসটি হ'ল আপনি স্টোরিবোর্ডেও এটি ব্যবহারকারীর সংজ্ঞায়িত রানটাইম অ্যাট্রিবিউট যুক্ত করে ব্যবহার করতে পারেন। দুঃখিতভাবে, UIEdgeInsetsএকটি টাইপ সেখানে সরাসরি পাওয়া যায় না কিন্তু যেহেতু CGRectচার সঙ্গে একটি struct নিয়ে গঠিত CGFloatএটি "আয়তক্ষেত্র" চয়ন করা এবং এই মত মান পূরণ করে flawlessly কাজ করে: {{top, left}, {bottom, right}}


6

মার্ফিনটি সামঞ্জস্য করার জন্য একটি ইন্টারফেস বিল্ডার সম্পত্তি সক্ষম করার জন্য আমি সুইফটে নিম্নবর্গটি ব্যবহার করছি:

@IBDesignable
class ALExtendedButton: UIButton {

    @IBInspectable var touchMargin:CGFloat = 20.0

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        var extendedArea = CGRectInset(self.bounds, -touchMargin, -touchMargin)
        return CGRectContainsPoint(extendedArea, point)
    }
}

কীভাবে ম্যানুয়ালি এই পয়েন্টটি ইনসাইড পরিবর্তন করবেন? যদি আমি পুরানো মার্জিনের সাথে একটি ইউআইবাটন তৈরি করেছি তবে এখন আমি এটি পরিবর্তন করতে চাই?
টমসওয়ায়ার

5

ঠিক আছে, আপনি আপনার ইউআইবাটনটি একটি স্বচ্ছ এবং কিছুটা বড় ইউআইভিউয়ের ভিতরে রাখতে পারেন এবং তারপরে ইউআইভিউটন হিসাবে ইউআইভিউ ইভেন্টে স্পর্শ ইভেন্টগুলি ধরতে পারেন। এইভাবে, আপনার বোতামটি এখনও থাকবে তবে আরও বড় স্পর্শ অঞ্চল সহ with ব্যবহারকারী বাটনটির পরিবর্তে দৃশ্যটি স্পর্শ করলে আপনাকে ম্যানুয়ালি নির্বাচিত ও হাইলাইটেড স্টেটগুলির সাথে মোকাবেলা করতে হবে।

অন্যান্য সম্ভাবনার মধ্যে একটি ইউআইবাটন পরিবর্তে একটি ইউআইআইমেজ ব্যবহার জড়িত।


5

আমি প্রোগ্রামটিমে তথ্য বোতামের হিট অঞ্চল বাড়াতে সক্ষম হয়েছি। "আমি" গ্রাফিক স্কেল পরিবর্তন করে না এবং নতুন বোতামের ফ্রেমে কেন্দ্রীভূত থাকে।

ইন্টারফেস বিল্ডারে তথ্য বোতামের আকারটি 18x19 [*] এ স্থির করা হয়েছে বলে মনে হচ্ছে। এটি একটি আইবিউলেটলে সংযুক্ত করে আমি কোনও সমস্যা ছাড়াই কোডের ফ্রেমের আকার পরিবর্তন করতে সক্ষম হয়েছি।

static void _resizeButton( UIButton *button )
{
    const CGRect oldFrame = infoButton.frame;
    const CGFloat desiredWidth = 44.f;
    const CGFloat margin = 
        ( desiredWidth - CGRectGetWidth( oldFrame ) ) / 2.f;
    infoButton.frame = CGRectInset( oldFrame, -margin, -margin );
}

[*]: আইওএসের পরবর্তী সংস্করণগুলি তথ্য বোতামের হিট অঞ্চল বাড়িয়েছে বলে মনে হয়।


5

এটি আমার সুইফট 3 সমাধান (এই ব্লগপোস্টের ভিত্তিতে: http://bdunagan.com/2010/03/01/iphone-tip-larger-hit-area- for- uibutton/ )

class ExtendedHitAreaButton: UIButton {

    @IBInspectable var hitAreaExtensionSize: CGSize = CGSize(width: -10, height: -10)

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

        let extendedFrame: CGRect = bounds.insetBy(dx: hitAreaExtensionSize.width, dy: hitAreaExtensionSize.height)

        return extendedFrame.contains(point) ? self : nil
    }
}

3

চেসের কাস্টম হিট পরীক্ষাটি ইউআইবাটনটির একটি সাবক্লাস হিসাবে প্রয়োগ করা হয়েছে। উদ্দেশ্য-সি লিখিত।

এটি উভয় initএবং buttonWithType:নির্মাণকারীর জন্য কাজ করে বলে মনে হচ্ছে । আমার প্রয়োজনের জন্য এটি নিখুঁত, তবে যেহেতু সাবক্লাসিং UIButtonলোমশ হতে পারে, কারও এতে দোষ আছে কিনা তা জানতে আগ্রহী।

CustomeHitAreaButton.h

#import <UIKit/UIKit.h>

@interface CustomHitAreaButton : UIButton

- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets;

@end

CustomHitAreaButton.m

#import "CustomHitAreaButton.h"

@interface CustomHitAreaButton()

@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

@implementation CustomHitAreaButton

- (instancetype)initWithFrame:(CGRect)frame {
    if(self = [super initWithFrame:frame]) {
        self.hitTestEdgeInsets = UIEdgeInsetsZero;
    }
    return self;
}

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    self->_hitTestEdgeInsets = hitTestEdgeInsets;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }
    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

@end

3

ওভাররাইড মাধ্যমে উত্তোলন UIButton।

সুইফট ২.২ :

// don't forget that negative values are for outset
_button.hitOffset = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
...
class UICustomButton: UIButton {
    var hitOffset = UIEdgeInsets()

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        guard hitOffset != UIEdgeInsetsZero && enabled && !hidden else {
            return super.pointInside(point, withEvent: event)
        }
        return UIEdgeInsetsInsetRect(bounds, hitOffset).contains(point)
    }
}

2

WJBackgroundInsetButton.h

#import <UIKit/UIKit.h>

@interface WJBackgroundInsetButton : UIButton {
    UIEdgeInsets backgroundEdgeInsets_;
}

@property (nonatomic) UIEdgeInsets backgroundEdgeInsets;

@end

WJBackgroundInsetButton.m

#import "WJBackgroundInsetButton.h"

@implementation WJBackgroundInsetButton

@synthesize backgroundEdgeInsets = backgroundEdgeInsets_;

-(CGRect) backgroundRectForBounds:(CGRect)bounds {
    CGRect sup = [super backgroundRectForBounds:bounds];
    UIEdgeInsets insets = self.backgroundEdgeInsets;
    CGRect r = UIEdgeInsetsInsetRect(sup, insets);
    return r;
}

@end

2

বিভাগে কখনও ওভাররাইড পদ্ধতি। সাবক্লাস বোতাম এবং ওভাররাইড - pointInside:withEvent:। উদাহরণস্বরূপ, যদি আপনার বোতামটির পাশ 44 পিক্সেলের চেয়ে কম হয় (যা সর্বনিম্ন টেপযোগ্য অঞ্চল হিসাবে প্রস্তাবিত হয়) এটি ব্যবহার করুন:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    return (ABS(point.x - CGRectGetMidX(self.bounds)) <= MAX(CGRectGetMidX(self.bounds), 22)) && (ABS(point.y - CGRectGetMidY(self.bounds)) <= MAX(CGRectGetMidY(self.bounds), 22));
}

2

আমি এই উদ্দেশ্যে একটি লাইব্রেরি তৈরি করেছি ।

আপনি একটি UIViewবিভাগ ব্যবহার করতে বেছে নিতে পারেন , কোন উপশ্রেণীর প্রয়োজন নেই :

@interface UIView (KGHitTesting)
- (void)setMinimumHitTestWidth:(CGFloat)width height:(CGFloat)height;
@end

অথবা আপনি আপনার ইউআইভিউ বা ইউআইবাটন সাবক্লাস করতে পারেন এবং minimumHitTestWidthএবং / অথবা সেট করতে পারেনminimumHitTestHeight । আপনার বোতামের হিট-পরীক্ষা অঞ্চলটি এই 2 টি মান দ্বারা প্রতিনিধিত্ব করা হবে।

অন্যান্য সমাধানগুলির মতোই, এটিও - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)eventপদ্ধতিটি ব্যবহার করে । আইওএস হিট-টেস্টিংয়ের সময় পদ্ধতিটি বলা হয়। আইওএস হিট-টেস্টিং কীভাবে কাজ করে সে সম্পর্কে এই ব্লগ পোস্টটিতে একটি ভাল বর্ণনা রয়েছে।

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

@property (nonatomic) CGFloat minimumHitTestHeight; 
@property (nonatomic) CGFloat minimumHitTestWidth;

@end

আপনি কেবল সাবক্লাস এবং কোনও কোড না লিখে ইন্টারফেস বিল্ডার ব্যবহার করতে পারেন: এখানে চিত্র বর্ণনা লিখুন


1
আমি কেবল গতির স্বার্থে এটি ইনস্টল করেছিলাম। এটি একসাথে রাখার জন্য আইবিআইস্পেসটেবল যুক্ত করা একটি দুর্দান্ত ধারণা ছিল।
user3344977

2

উপরের গিয়াসেটের উত্তরের ভিত্তিতে (যা আমি সবচেয়ে মার্জিত সমাধান পেয়েছি), এখানে সুইফট 3 সংস্করণ রয়েছে:

import UIKit

fileprivate let minimumHitArea = CGSize(width: 44, height: 44)

extension UIButton {
    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // if the button is hidden/disabled/transparent it can't be hit
        if isHidden || !isUserInteractionEnabled || alpha < 0.01 { return nil }

        // increase the hit frame to be at least as big as `minimumHitArea`
        let buttonSize = bounds.size
        let widthToAdd = max(minimumHitArea.width - buttonSize.width, 0)
        let heightToAdd = max(minimumHitArea.height - buttonSize.height, 0)
        let largerFrame = bounds.insetBy(dx: -widthToAdd / 2, dy: -heightToAdd / 2)

        // perform hit test on larger frame
        return (largerFrame.contains(point)) ? self : nil
    }
}

2

এটি কেবল এই সহজ:

class ChubbyButton: UIButton {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        return bounds.insetBy(dx: -20, dy: -20).contains(point)
    }
}

এটাই পুরো জিনিস।


1

আমি টেবিলভিউসেল.অ্যাকসেসরিভিউয়ের বোতামের জন্য এটির স্পর্শের ক্ষেত্রটি প্রসারিত করতে এই কৌশলটি ব্যবহার করছি

#pragma mark - Touches

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch                  = [touches anyObject];
    CGPoint location                = [touch locationInView:self];
    CGRect  accessoryViewTouchRect  = CGRectInset(self.accessoryView.frame, -15, -15);

    if(!CGRectContainsPoint(accessoryViewTouchRect, location))
        [super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch                  = [touches anyObject];
    CGPoint location                = [touch locationInView:self];
    CGRect  accessoryViewTouchRect  = CGRectInset(self.accessoryView.frame, -15, -15);

    if(CGRectContainsPoint(accessoryViewTouchRect, location) && [self.accessoryView isKindOfClass:[UIButton class]])
    {
        [(UIButton *)self.accessoryView sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
    else
        [super touchesEnded:touches withEvent:event];
}

1

আমি সবেমাত্র ২.২ এ @ চেসে সমাধানের বন্দরটি করেছি

import Foundation
import ObjectiveC

private var hitTestEdgeInsetsKey: UIEdgeInsets = UIEdgeInsetsZero

extension UIButton {
    var hitTestEdgeInsets:UIEdgeInsets {
        get {
            let inset = objc_getAssociatedObject(self, &hitTestEdgeInsetsKey) as? NSValue ?? NSValue(UIEdgeInsets: UIEdgeInsetsZero)
            return inset.UIEdgeInsetsValue()
        }
        set {
            let inset = NSValue(UIEdgeInsets: newValue)
            objc_setAssociatedObject(self, &hitTestEdgeInsetsKey, inset, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    public override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        guard !UIEdgeInsetsEqualToEdgeInsets(hitTestEdgeInsets, UIEdgeInsetsZero) && self.enabled == true && self.hidden == false else {
            return super.pointInside(point, withEvent: event)
        }
        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets)
        return CGRectContainsPoint(hitFrame, point)
    }
}

একটি আপনি এই মত ব্যবহার করতে পারেন

button.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10)

অন্য যে কোনও রেফারেন্সের জন্য https://stackoverflow.com/a/13067285/1728552 দেখুন


1

@ এন্টোইনের উত্তরটি সুইফট 4-এর জন্য বিন্যাসিত

class ExtendedHitButton: UIButton
{
    override func point( inside point: CGPoint, with event: UIEvent? ) -> Bool
    {
        let relativeFrame = self.bounds
        let hitTestEdgeInsets = UIEdgeInsetsMake( -44, -44, -44, -44 ) // Apple recommended hit target
        let hitFrame = UIEdgeInsetsInsetRect( relativeFrame, hitTestEdgeInsets )
        return hitFrame.contains( point );
    }
}

0

আমি চেজের প্রতিক্রিয়া অনুসরণ করেছি এবং এটি দুর্দান্ত কাজ করে, আপনি যখন আরিয়াকে তৈরি করেন তখন একটি বড় সমস্যা, সেই অঞ্চলটির চেয়ে বড় যেখানে বোতামটি অনির্বাচিত হয়ে যায় (যদি অঞ্চলটি বড় না হয়) এটি ইউআইসিএন্ট্রোলটেন্টটিউচআইপি ইনসাইড ইভেন্টের জন্য নির্বাচককে কল করে না ।

আমি মনে করি যে কোনও দিক বা এর মতো কোনও আকার 200 এরও বেশি।


0

আমি এই গেমটি থেকে অনেক দেরিতে এসেছি, তবে একটি সাধারণ কৌশল নিয়ে ভাবতে চেয়েছি যা আপনার সমস্যার সমাধান করতে পারে। এখানে আমার জন্য একটি আদর্শ প্রোগ্রামিয়াল ইউআইবাটন স্নিপেট রয়েছে:

UIImage *arrowImage = [UIImage imageNamed:@"leftarrow"];
arrowButton = [[UIButton alloc] initWithFrame:CGRectMake(15.0, self.frame.size.height-35.0, arrowImage.size.width/2, arrowImage.size.height/2)];
[arrowButton setBackgroundImage:arrowImage forState:UIControlStateNormal];
[arrowButton addTarget:self action:@selector(onTouchUp:) forControlEvents:UIControlEventTouchUpOutside];
[arrowButton addTarget:self action:@selector(onTouchDown:) forControlEvents:UIControlEventTouchDown];
[arrowButton addTarget:self action:@selector(onTap:) forControlEvents:UIControlEventTouchUpInside];
[arrowButton addTarget:self action:@selector(onTouchUp:) forControlEvents:UIControlEventTouchDragExit];
[arrowButton setUserInteractionEnabled:TRUE];
[arrowButton setAdjustsImageWhenHighlighted:NO];
[arrowButton setTag:1];
[self addSubview:arrowButton];

আমি আমার বোতামটির জন্য একটি স্বচ্ছ পিএনজি চিত্র লোড করছি এবং পটভূমি চিত্রটি সেট করছি। আমি ইউআইআইমেজ এর ভিত্তিতে ফ্রেম সেট করছি এবং রেটিনার জন্য 50% দ্বারা স্কেলিং করছি। ঠিক আছে, আপনি উপরের সাথে একমত হতে পারেন বা না, তবে আপনি যদি হিট অঞ্চলটিকে আরও বড় করে তুলতে চান এবং নিজেকে মাথা ব্যথা বাঁচাতে চান:

আমি যা করি, ফটোশপটিতে চিত্রটি খুলুন এবং ক্যানভাসের আকারটি 120% বাড়িয়ে সংরক্ষণ করুন। কার্যকরভাবে আপনি স্বচ্ছ পিক্সেল সহ চিত্রটি আরও বড় করেছেন।

শুধু একটি পদ্ধতির।


0

উপরে Jlajlar এর উত্তরটি ভাল এবং সোজাসাপ্টা মনে হলেও Xamarin.iOS এর সাথে মেলে না, তাই আমি এটিকে জামারিনে রূপান্তরিত করেছি। আপনি যদি কোনও জ্যামারিন আইওএসের সমাধান খুঁজছেন তবে এখানে এটি চলে:

public override bool PointInside (CoreGraphics.CGPoint point, UIEvent uievent)
{
    var margin = -10f;
    var area = this.Bounds;
    var expandedArea = area.Inset(margin, margin);
    return expandedArea.Contains(point);
}

আপনি এই পদ্ধতিটি ক্লাসে যুক্ত করতে পারেন যেখানে আপনি ইউআইভিউ বা ইউআইআইমেজভিউর উপর নজর রেখেছেন। এটি দুর্দান্তভাবে কাজ করেছে :)


0

উত্তরগুলির কোনওটিই আমার পক্ষে উপযুক্ত নয়, কারণ আমি ব্যাকগ্রাউন্ড চিত্র এবং সেই বোতামটিতে একটি শিরোনাম ব্যবহার করি। তদুপরি, বোতামটি পর্দার আকার পরিবর্তনের সাথে সাথে আকার পরিবর্তন করবে।

পরিবর্তে, আমি পিএনজি স্বচ্ছ অঞ্চলটিকে বৃহত্তর করে ট্যাপের ক্ষেত্রটি প্রসারিত করি।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.